diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 266a6152d621..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,29 +0,0 @@ - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..e12d999ad6d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Community Support + url: https://stackoverflow.com/tags/spring-boot + about: Please ask and answer questions on StackOverflow with the tag `spring-boot`. diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 000000000000..e94a911d37cd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,37 @@ +--- +name: General +about: Bugs, enhancements, documentation, tasks. +title: '' +labels: '' +assignees: '' +--- + + diff --git a/.github/actions/await-http-resource/action.yml b/.github/actions/await-http-resource/action.yml new file mode 100644 index 000000000000..ba177fb757b5 --- /dev/null +++ b/.github/actions/await-http-resource/action.yml @@ -0,0 +1,20 @@ +name: Await HTTP Resource +description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)' +inputs: + url: + description: 'URL of the resource to await' + required: true +runs: + using: composite + steps: + - name: Await HTTP resource + shell: bash + run: | + url=${{ inputs.url }} + echo "Waiting for $url" + until curl --fail --head --silent ${{ inputs.url }} > /dev/null + do + echo "." + sleep 60 + done + echo "$url is available" diff --git a/.github/actions/create-github-release/action.yml b/.github/actions/create-github-release/action.yml index 8137274282fc..50b382f27715 100644 --- a/.github/actions/create-github-release/action.yml +++ b/.github/actions/create-github-release/action.yml @@ -4,6 +4,10 @@ inputs: milestone: description: 'Name of the GitHub milestone for which a release will be created' required: true + pre-release: + description: 'Whether the release is a pre-release (a milestone or release candidate)' + required: false + default: 'false' token: description: 'Token to use for authentication with GitHub' required: true @@ -23,4 +27,4 @@ runs: shell: bash env: GITHUB_TOKEN: ${{ inputs.token }} - 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/sync-to-maven-central/action.yml b/.github/actions/sync-to-maven-central/action.yml index 543b96c24ade..74dd611fc728 100644 --- a/.github/actions/sync-to-maven-central/action.yml +++ b/.github/actions/sync-to-maven-central/action.yml @@ -38,13 +38,6 @@ runs: upload: true username: ${{ inputs.ossrh-s01-token-username }} - name: Await - shell: bash - run: | - url=${{ format('https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot/{0}/spring-boot-{0}.jar', inputs.spring-boot-version) }} - echo "Waiting for $url" - until curl --fail --head --silent $url > /dev/null - do - echo "." - sleep 60 - done - echo "$url is available" + uses: ./.github/actions/await-http-resource + with: + url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot/{0}/spring-boot-{0}.jar', inputs.spring-boot-version) }} diff --git a/.github/actions/update-homebrew-tap/action.yml b/.github/actions/update-homebrew-tap/action.yml new file mode 100644 index 000000000000..43a1e8b77d9e --- /dev/null +++ b/.github/actions/update-homebrew-tap/action.yml @@ -0,0 +1,36 @@ +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: Await Formula + uses: ./.github/actions/await-http-resource + with: + url: 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 + - 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 7ad01cf6c6a3..9bb892337877 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.2.x + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: @@ -27,9 +27,7 @@ jobs: - name: Deploy uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 with: - artifact-properties: | - /**/spring-boot-docs-*.zip::zip.type=docs,zip.deployed=false - build-name: ${{ vars.COMMERCIAL && format('spring-boot-commercial-{0}', github.ref_name) || format('spring-boot-{0}', github.ref_name) }} + build-name: ${{ vars.COMMERCIAL && format('spring-boot-commercial-{0}', '3.4.x') || format('spring-boot-{0}', '3.4.x') }} folder: 'deployment-repository' password: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_PASSWORD || secrets.ARTIFACTORY_PASSWORD }} project: ${{ vars.COMMERCIAL && 'spring' }} @@ -48,6 +46,17 @@ jobs: webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} outputs: version: ${{ steps.build-and-publish.outputs.version }} + trigger-docs-build: + name: Trigger Docs Build + needs: build-and-deploy-snapshot + permissions: + actions: write + runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} + 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/ci.yml b/.github/workflows/ci.yml index 65a812a9ed8f..01fc9ba6720d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: branches: - - '3.2.x' + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: @@ -22,9 +22,9 @@ jobs: - version: 17 toolchain: false - version: 21 - toolchain: true + toolchain: false - version: 22 - toolchain: true + toolchain: false - version: 23 toolchain: true exclude: diff --git a/.github/workflows/release-milestone.yml b/.github/workflows/release-milestone.yml new file mode 100644 index 000000000000..9e8e596c6ed0 --- /dev/null +++ b/.github/workflows/release-milestone.yml @@ -0,0 +1,92 @@ +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 + if: ${{ github.repository == 'spring-projects/spring-boot' }} + runs-on: ${{ vars.UBUNTU_MEDIUIM || 'ubuntu-latest' }} + 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 }} + gradle-cache-read-only: false + publish: true + - 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' + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + repository: 'libs-staging-local' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + outputs: + version: ${{ steps.build-and-publish.outputs.version }} + verify: + name: Verify + needs: build-and-stage-release + uses: ./.github/workflows/verify.yml + secrets: + commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_RO_PASSWORD }} + commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_RO_USERNAME }} + google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }} + opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }} + opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }} + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + with: + staging: true + version: ${{ needs.build-and-stage-release.outputs.version }} + promote-release: + name: Promote Release + needs: + - build-and-stage-release + - verify + runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} + steps: + - name: Set up JFrog CLI + uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1 + 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 + trigger-docs-build: + name: Trigger Docs Build + needs: + - build-and-stage-release + - verify + permissions: + actions: write + runs-on: ubuntu-latest + 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-stage-release.outputs.version }} + create-github-release: + name: Create GitHub Release + needs: + - build-and-stage-release + - promote-release + - trigger-docs-build + runs-on: ${{ vars.UBUNTU_SMALL || '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 }} + pre-release: true + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7eb895148261..17a8496e5abd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release on: push: tags: - - v3.2.[0-9]+ + - v3.4.[0-9]+ concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: @@ -27,8 +27,6 @@ jobs: - name: Stage Release uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 with: - artifact-properties: | - /**/spring-boot-docs-*.zip::zip.type=docs,zip.deployed=false build-name: ${{ vars.COMMERCIAL && format('spring-boot-commercial-{0}', steps.build-and-publish.outputs.version) || format('spring-boot-{0}', steps.build-and-publish.outputs.version) }} folder: 'deployment-repository' password: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_PASSWORD || secrets.ARTIFACTORY_PASSWORD }} @@ -119,10 +117,37 @@ jobs: - name: Publish to SDKMAN! uses: ./.github/actions/publish-to-sdkman with: - make-default: false + 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: ${{ vars.UBUNTU_SMALL || '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 }} + trigger-docs-build: + name: Trigger Docs Build + needs: + - build-and-stage-release + - sync-to-maven-central + permissions: + actions: write + runs-on: ubuntu-latest + 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-stage-release.outputs.version }} create-github-release: name: Create GitHub Release needs: @@ -130,6 +155,8 @@ jobs: - promote-release - publish-gradle-plugin - publish-to-sdkman + - trigger-docs-build + - update-homebrew-tap runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} steps: - name: Check Out Code diff --git a/.github/workflows/run-system-tests.yml b/.github/workflows/run-system-tests.yml index c0a6e71e5cef..0b7607fde848 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.2.x' + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: diff --git a/.github/workflows/trigger-docs-build.yml b/.github/workflows/trigger-docs-build.yml new file mode 100644 index 000000000000..e583dd88589a --- /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: 'Git refname to build (e.g., 1.0.x)' + required: false + build-version: + description: 'Version being build (e.g. 1.0.3-SNAPSHOT)' + required: false +permissions: + actions: write +jobs: + trigger-docs-build: + name: Trigger Docs Build + if: github.repository_owner == 'spring-projects' + runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }} + 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/.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/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 96ba64beb63d..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 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 f570bfec5fcc..11095f59c5c0 100755 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,6 @@ -= Spring Boot image:https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=3.2.x["Build Status", link="https://github.com/spring-projects/spring-boot/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A3.2.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"] += 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/docs/3.2.x-SNAPSHOT/reference +: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. @@ -19,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. @@ -61,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: @@ -75,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. @@ -111,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. @@ -120,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. @@ -127,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. @@ -135,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. @@ -143,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 +189,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..05db0327412f --- /dev/null +++ b/antora/package-lock.json @@ -0,0 +1,3341 @@ +{ + "name": "antora", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "hasInstallScript": true, + "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.11", + "patch-package": "^8.0.0" + } + }, + "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.11", + "resolved": "https://registry.npmjs.org/@springio/asciidoctor-extensions/-/asciidoctor-extensions-1.0.0-alpha.11.tgz", + "integrity": "sha512-U+uTAdlqv1qT66iI6M3xHUgJMLl3KxoduiNjhpUGDzLC1PBuApp//BOPF7vWyJT9IGO9pmrQ0Moeucs5xvovQg==", + "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/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, + "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/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "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/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "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/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, + "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-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "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-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "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": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "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/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "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/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.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/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "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/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "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/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "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/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "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/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, + "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/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.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/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "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..16a0e87f396a --- /dev/null +++ b/antora/package.json @@ -0,0 +1,20 @@ +{ + "scripts": { + "antora": "node npm/antora.js", + "postinstall": "patch-package" + }, + "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.11", + "patch-package": "^8.0.0" + }, + "config": { + "ui-bundle-url": "https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.17/ui-bundle.zip" + } +} diff --git a/antora/patches/@vscode+gulp-vinyl-zip+2.5.0.patch b/antora/patches/@vscode+gulp-vinyl-zip+2.5.0.patch new file mode 100644 index 000000000000..c47c0a27efa2 --- /dev/null +++ b/antora/patches/@vscode+gulp-vinyl-zip+2.5.0.patch @@ -0,0 +1,285 @@ +diff --git a/node_modules/@vscode/gulp-vinyl-zip/lib/src/index.js b/node_modules/@vscode/gulp-vinyl-zip/lib/src/index.js +index 17d902d..0448dec 100644 +--- a/node_modules/@vscode/gulp-vinyl-zip/lib/src/index.js ++++ b/node_modules/@vscode/gulp-vinyl-zip/lib/src/index.js +@@ -1,135 +1,157 @@ +-'use strict'; +- +-var fs = require('fs'); +-var constants = fs.constants; +-var yauzl = require('yauzl'); +-var File = require('../vinyl-zip'); +-var queue = require('queue'); +-var through = require('through'); +-var map = require('through2').obj; +- +-function modeFromEntry(entry) { +- var attr = entry.externalFileAttributes >> 16 || 33188; +- +- // The following constants are not available on all platforms: +- // 448 = constants.S_IRWXU, 56 = constants.S_IRWXG, 7 = constants.S_IRWXO +- return [448, 56, 7] +- .map(function (mask) { return attr & mask; }) +- .reduce(function (a, b) { return a + b; }, attr & constants.S_IFMT); ++'use strict' ++ ++// This is fork of vinyl-zip with the following updates: ++// - unzipFile has an additional `.on('error'` handler ++// - toStream has an additional `zip.on('error'` handler ++ ++const fs = require('fs') ++const constants = fs.constants ++const yauzl = require('yauzl') ++const File = require('vinyl') ++const queue = require('queue') ++const through = require('through') ++const map = require('through2').obj ++ ++function modeFromEntry (entry) { ++ const attr = entry.externalFileAttributes >> 16 || 33188 ++ return [448, 56, 7] ++ .map(function (mask) { ++ return attr & mask ++ }) ++ .reduce(function (a, b) { ++ return a + b ++ }, attr & constants.S_IFMT) + } + +-function mtimeFromEntry(entry) { +- return yauzl.dosDateTimeToDate(entry.lastModFileDate, entry.lastModFileTime); ++function mtimeFromEntry (entry) { ++ return yauzl.dosDateTimeToDate(entry.lastModFileDate, entry.lastModFileTime) + } + +-function toStream(zip) { +- var result = through(); +- var q = queue(); +- var didErr = false; +- +- q.on('error', function (err) { +- didErr = true; +- result.emit('error', err); +- }); +- +- zip.on('entry', function (entry) { +- if (didErr) { return; } +- +- var stat = new fs.Stats(); +- stat.mode = modeFromEntry(entry); +- stat.mtime = mtimeFromEntry(entry); +- +- // directories +- if (/\/$/.test(entry.fileName)) { +- stat.mode = (stat.mode & ~constants.S_IFMT) | constants.S_IFDIR; +- } +- +- var file = { +- path: entry.fileName, +- stat: stat +- }; +- +- if (stat.isFile()) { +- stat.size = entry.uncompressedSize; +- if (entry.uncompressedSize === 0) { +- file.contents = Buffer.alloc(0); +- result.emit('data', new File(file)); +- } else { +- q.push(function (cb) { +- zip.openReadStream(entry, function (err, readStream) { +- if (err) { return cb(err); } +- file.contents = readStream; +- result.emit('data', new File(file)); +- cb(); +- }); +- }); +- +- q.start(); +- } +- } else if (stat.isSymbolicLink()) { +- stat.size = entry.uncompressedSize; +- q.push(function (cb) { +- zip.openReadStream(entry, function (err, readStream) { +- if (err) { return cb(err); } +- file.symlink = ''; +- readStream.on('data', function (c) { file.symlink += c; }); +- readStream.on('error', cb); +- readStream.on('end', function () { +- result.emit('data', new File(file)); +- cb(); +- }); +- }); +- }); +- +- q.start(); +- } else if (stat.isDirectory()) { +- result.emit('data', new File(file)); +- } else { +- result.emit('data', new File(file)); +- } +- }); +- +- zip.on('end', function () { +- if (didErr) { +- return; +- } +- +- if (q.length === 0) { +- result.end(); +- } else { +- q.on('end', function () { +- result.end(); +- }); +- } +- }); +- +- return result; ++function toStream (zip) { ++ const result = through() ++ const q = queue() ++ let didErr = false ++ ++ q.on('error', function (err) { ++ didErr = true ++ result.emit('error', err) ++ }) ++ ++ zip.on('error', function (err) { ++ didErr = true ++ result.emit('error', err) ++ }) ++ ++ zip.on('entry', function (entry) { ++ if (didErr) { ++ return ++ } ++ ++ const stat = new fs.Stats() ++ stat.mode = modeFromEntry(entry) ++ stat.mtime = mtimeFromEntry(entry) ++ ++ // directories ++ if (/\/$/.test(entry.fileName)) { ++ stat.mode = (stat.mode & ~constants.S_IFMT) | constants.S_IFDIR ++ } ++ ++ const file = { ++ path: entry.fileName, ++ stat, ++ } ++ ++ if (stat.isFile()) { ++ stat.size = entry.uncompressedSize ++ if (entry.uncompressedSize === 0) { ++ file.contents = Buffer.alloc(0) ++ result.emit('data', new File(file)) ++ } else { ++ q.push(function (cb) { ++ zip.openReadStream(entry, function (err, readStream) { ++ if (err) { ++ return cb(err) ++ } ++ file.contents = readStream ++ result.emit('data', new File(file)) ++ cb() ++ }) ++ }) ++ ++ q.start() ++ } ++ } else if (stat.isSymbolicLink()) { ++ stat.size = entry.uncompressedSize ++ q.push(function (cb) { ++ zip.openReadStream(entry, function (err, readStream) { ++ if (err) { ++ return cb(err) ++ } ++ file.symlink = '' ++ readStream.on('data', function (c) { ++ file.symlink += c ++ }) ++ readStream.on('error', cb) ++ readStream.on('end', function () { ++ result.emit('data', new File(file)) ++ cb() ++ }) ++ }) ++ }) ++ ++ q.start() ++ } else if (stat.isDirectory()) { ++ result.emit('data', new File(file)) ++ } else { ++ result.emit('data', new File(file)) ++ } ++ }) ++ ++ zip.on('end', function () { ++ if (didErr) { ++ return ++ } ++ ++ if (q.length === 0) { ++ result.end() ++ } else { ++ q.on('end', function () { ++ result.end() ++ }) ++ } ++ }) ++ ++ return result + } + +-function unzipFile(zipPath) { +- var result = through(); +- yauzl.open(zipPath, function (err, zip) { +- if (err) { return result.emit('error', err); } +- toStream(zip).pipe(result); +- }); +- return result; ++function unzipFile (zipPath) { ++ const result = through() ++ yauzl.open(zipPath, function (err, zip) { ++ if (err) { ++ return result.emit('error', err) ++ } ++ toStream(zip) ++ .on('error', (err) => result.emit('error', err)) ++ .pipe(result) ++ }) ++ return result + } + +-function unzip() { +- return map(function (file, enc, next) { +- if (!file.isBuffer()) return next(new Error('Only supports buffers')); +- yauzl.fromBuffer(file.contents, (err, zip) => { +- if (err) return this.emit('error', err); +- toStream(zip) +- .on('error', next) +- .on('data', (data) => this.push(data)) +- .on('end', next); +- }); +- }); ++function unzip () { ++ return map(function (file, enc, next) { ++ if (!file.isBuffer()) return next(new Error('Only supports buffers')) ++ yauzl.fromBuffer(file.contents, (err, zip) => { ++ if (err) return this.emit('error', err) ++ toStream(zip) ++ .on('error', next) ++ .on('data', (data) => this.push(data)) ++ .on('end', next) ++ }) ++ }) + } + +-function src(zipPath) { +- return zipPath ? unzipFile(zipPath) : unzip(); ++function src (zipPath) { ++ return zipPath ? unzipFile(zipPath) : unzip() + } + +-module.exports = src; ++module.exports = src diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index a8683f63a31c..933555b51b76 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -29,20 +29,24 @@ dependencies { implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) implementation("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}") + 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.3.0") implementation("commons-codec:commons-codec:${commonsCodecVersion}") implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0") implementation("dev.adamko.dokkatoo:dokkatoo-plugin:2.3.1") + 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:${mavenVersion}") - implementation("org.asciidoctor:asciidoctor-gradle-jvm:4.0.2") + implementation("org.antora:gradle-antora-plugin:1.0.0") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}") implementation("org.springframework:spring-context") implementation("org.springframework:spring-core") implementation("org.springframework:spring-web") - implementation("io.spring.nohttp:nohttp-gradle:0.0.11") + implementation("org.yaml:snakeyaml:${snakeYamlVersion}") testImplementation("org.assertj:assertj-core:${assertjVersion}") testImplementation("org.hamcrest:hamcrest:${hamcrestVersion}") @@ -53,6 +57,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 { @@ -122,9 +132,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..698e0939f2b4 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java @@ -0,0 +1,217 @@ +/* + * 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.file.Directory; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.provider.Provider; +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) { + Provider nodeProjectDir = getNodeProjectDir(project); + generateAntoraPlaybookTask.getOutputFile() + .set(nodeProjectDir.map((directory) -> directory.file("antora-playbook.yml"))); + } + + private void configureCopyAntoraPackageJsonTask(Project project, Copy copyAntoraPackageJsonTask) { + copyAntoraPackageJsonTask + .from(project.getRootProject().file("antora"), + (spec) -> spec.include("package.json", "package-lock.json", "patches/**")) + .into(getNodeProjectDir(project)); + } + + 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", + project.getLayout().getBuildDirectory().file("generated/docs/antora-yml/antora.yml")); + generateAntoraYmlTask.setProperty("yml", getDefaultYml(project)); + generateAntoraYmlTask.getAsciidocAttributes().putAll(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 Provider> getAsciidocAttributes(Project project, + ExtractVersionConstraints dependencyVersionsTask) { + return project.provider(() -> { + 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) { + nodeExtension.getWorkDir().set(project.getLayout().getBuildDirectory().dir(".gradle/nodejs")); + nodeExtension.getNpmWorkDir().set(project.getLayout().getBuildDirectory().dir(".gradle/npm")); + nodeExtension.getNodeProjectDir().set(getNodeProjectDir(project)); + } + + private Provider getNodeProjectDir(Project project) { + return project.getLayout().getBuildDirectory().dir(".gradle/nodeproject"); + } + +} 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 17e2226226a8..000000000000 --- a/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java +++ /dev/null @@ -1,168 +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.build; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -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.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.Sync; - -import org.springframework.boot.build.artifacts.ArtifactRelease; -import org.springframework.boot.build.properties.BuildProperties; -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. - *
    • The task is configured to depend on the {@code asciidoctorExtensions} configuraion - * to ensure that any extensions are built before the task runs. This works around - * https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/721. - *
    - *
- * - * @author Andy Wilkinson - * @author Scott Frederick - */ -class AsciidoctorConventions { - - private static final String ASCIIDOCTORJ_VERSION = "2.4.3"; - - void apply(Project project) { - project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> { - makeAllWarningsFatal(project); - upgradeAsciidoctorJVersion(project); - Configuration asciidoctorExtensions = createAsciidoctorExtensionsConfiguration(project); - project.getTasks() - .withType(AbstractAsciidoctorTask.class, - (asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask, asciidoctorExtensions)); - }); - } - - 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 Configuration createAsciidoctorExtensionsConfiguration(Project project) { - return project.getConfigurations().create("asciidoctorExtensions", (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, - Configuration asciidoctorExtensions) { - asciidoctorTask.configurations(asciidoctorExtensions); - asciidoctorTask.dependsOn(asciidoctorExtensions); - configureCommonAttributes(project, asciidoctorTask); - configureOptions(asciidoctorTask); - configureForkOptions(asciidoctorTask); - asciidoctorTask.baseDirFollowsSourceDir(); - createSyncDocumentationSourceTask(project, asciidoctorTask); - if (asciidoctorTask instanceof AsciidoctorTask task) { - boolean pdf = task.getName().toLowerCase(Locale.ROOT).contains("pdf"); - String backend = (!pdf) ? "spring-html" : "spring-pdf"; - task.outputOptions((outputOptions) -> outputOptions.backends(backend)); - } - } - - private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) { - String buildType = BuildProperties.get(project).buildType().toIdentifier(); - ArtifactRelease artifacts = ArtifactRelease.forProject(project); - Map attributes = new HashMap<>(); - attributes.put("attribute-missing", "warn"); - attributes.put("github-tag", determineGitHubTag(project)); - attributes.put("build-type", buildType); - attributes.put("artifact-release-type", artifacts.getType()); - attributes.put("build-and-artifact-release-type", buildType + "-" + artifacts.getType()); - attributes.put("artifact-download-repo", artifacts.getDownloadRepo()); - attributes.put("revnumber", project.getVersion()); - asciidoctorTask.attributes(attributes); - } - - // See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597 - private void configureForkOptions(AbstractAsciidoctorTask asciidoctorTask) { - asciidoctorTask.jvm((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.2.x" : version; - } - - private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) { - asciidoctorTask.options(Collections.singletonMap("doctype", "book")); - } - - private void createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask) { - Sync syncDocumentationSource = project.getTasks() - .create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class); - File syncedSource = project.getLayout() - .getBuildDirectory() - .dir("docs/src/" + asciidoctorTask.getName()) - .get() - .getAsFile(); - 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/"))); - } - -} 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 5d3850a0126f..8bd7f2fb21d1 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java @@ -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 @@ -46,7 +46,7 @@ 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/JavaConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java index 1ba8cd8293c3..e5ce6df4244f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java @@ -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/MavenPublishingConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java index 9bcd9fe892fa..8bc2fb6efdb0 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/MavenPublishingConventions.java @@ -104,9 +104,6 @@ private void customizePom(MavenPom pom, Project project) { } private void customizeJavaMavenPublication(MavenPublication publication, Project project) { - if (publication.getName().equals("pluginMaven")) { - return; - } publication.versionMapping((strategy) -> strategy.usage(Usage.JAVA_API, (mappingStrategy) -> mappingStrategy .fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME))); publication.versionMapping( 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..3153e7b201be --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.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.build.antora; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +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.boot.build.properties.BuildProperties; +import org.springframework.boot.build.properties.BuildType; +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 BuildType buildType; + + 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.buildType = BuildProperties.get(project).buildType(); + this.artifactRelease = ArtifactRelease.forProject(project); + this.libraries = dependencyBom.getLibraries(); + this.dependencyVersions = dependencyVersions; + this.projectProperties = project.getProperties(); + } + + AntoraAsciidocAttributes(String version, boolean latestVersion, BuildType buildType, List libraries, + Map dependencyVersions, Map projectProperties) { + this.version = version; + this.latestVersion = latestVersion; + this.buildType = buildType; + 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<>(); + addBuildTypeAttribute(attributes); + addGitHubAttributes(attributes); + addVersionAttributes(attributes); + addArtifactAttributes(attributes); + addUrlJava(attributes); + addUrlLibraryLinkAttributes(attributes); + addPropertyAttributes(attributes); + return attributes; + } + + private void addBuildTypeAttribute(Map attributes) { + attributes.put("build-type", this.buildType.toIdentifier()); + } + + 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")); + addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations"); + addDependencyVersion(attributes, "jackson-core", "com.fasterxml.jackson.core:jackson-core"); + addDependencyVersion(attributes, "jackson-databind", "com.fasterxml.jackson.core:jackson-databind"); + addSpringDataDependencyVersion(attributes, "spring-data-commons"); + addSpringDataDependencyVersion(attributes, "spring-data-couchbase"); + addSpringDataDependencyVersion(attributes, "spring-data-cassandra"); + 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"); + addSpringDataDependencyVersion(attributes, "spring-data-ldap"); + } + + private void addSpringDataDependencyVersion(Map attributes, String artifactId) { + addSpringDataDependencyVersion(attributes, artifactId, artifactId); + } + + private void addSpringDataDependencyVersion(Map attributes, String name, String artifactId) { + String version = getVersion("org.springframework.data:" + artifactId); + String majorMinor = Arrays.stream(version.split("\\.")).limit(2).collect(Collectors.joining(".")); + String antoraVersion = version.endsWith(DASH_SNAPSHOT) ? majorMinor + DASH_SNAPSHOT : majorMinor; + attributes.put("version-" + name + "-docs", antoraVersion); + attributes.put("version-" + name + "-javadoc", majorMinor + ".x"); + } + + private void addDependencyVersion(Map attributes, String name, String groupAndArtifactId) { + attributes.put("version-" + name, getVersion(groupAndArtifactId)); + } + + private String getVersion(String groupAndArtifactId) { + String version = this.dependencyVersions.get(groupAndArtifactId); + Assert.notNull(version, () -> "No version found for " + groupAndArtifactId); + return version; + } + + private void addArtifactAttributes(Map attributes) { + attributes.put("url-artifact-repository", this.artifactRelease.getDownloadRepo()); + attributes.put("artifact-release-type", this.artifactRelease.getType()); + attributes.put("build-and-artifact-release-type", + this.buildType.toIdentifier() + "-" + this.artifactRelease.getType()); + } + + private void addUrlJava(Map attributes) { + attributes.put("url-javase-javadoc", "https://docs.oracle.com/en/java/javase/17/docs/api/"); + } + + 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..4f38141a5d3f --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java @@ -0,0 +1,191 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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/javadoc-extension", + "@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..adbf9e55a269 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.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.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.ArrayList; +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(); + + @Input + @Optional + public abstract Property getExcludeJavadocExtension(); + + 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); + filterJavadocExtension(data); + return data; + } + + @SuppressWarnings("unchecked") + private void filterJavadocExtension(Map data) { + if (getExcludeJavadocExtension().getOrElse(Boolean.FALSE)) { + Map asciidoc = (Map) data.get("asciidoc"); + List extensions = new ArrayList<>((List) asciidoc.get("extensions")); + extensions.remove("@springio/asciidoctor-extensions/javadoc-extension"); + asciidoc.put("extensions", extensions); + } + } + + @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().getLayout().getBuildDirectory().dir("site").get().getAsFile().toPath()); + data.put("output", Map.of("dir", "." + File.separator + playbookDir.relativize(outputDir))); + } + + @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 d4d01a7ff3d3..d4c22251752e 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 @@ -29,12 +29,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; @@ -62,11 +66,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 * @author Ivan Malutin */ public abstract class ArchitectureCheck extends DefaultTask { @@ -82,7 +89,7 @@ public ArchitectureCheck() { noClassesShouldCallStepVerifierStepVerifyComplete(), noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(), noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding(), - noClassesShouldCallStringToUpperCaseWithoutLocale(), + noClassesShouldLoadResourcesUsingResourceUtils(), noClassesShouldCallStringToUpperCaseWithoutLocale(), noClassesShouldCallStringToLowerCaseWithoutLocale()); getRules().addAll(getProhibitObjectsRequireNonNull() .map((prohibit) -> prohibit ? noClassesShouldCallObjectsRequireNonNull() : Collections.emptyList())); @@ -162,21 +169,23 @@ private ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoPa .and() .haveRawReturnType( Predicates.assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor")) - .should(haveNoParameters()) + .should(onlyInjectEnvironment()) .andShould() .beStatic() .allowEmptyShould(true); } - private ArchCondition haveNoParameters() { - return new ArchCondition<>("have no parameters") { + private ArchCondition onlyInjectEnvironment() { + return new ArchCondition<>("only inject Environment") { @Override public void check(JavaMethod item, ConditionEvents events) { List parameters = item.getParameters(); - if (!parameters.isEmpty()) { - events - .add(SimpleConditionEvent.violated(item, item.getDescription() + " should have no parameters")); + for (JavaParameter parameter : parameters) { + if (!"org.springframework.core.env.Environment".equals(parameter.getType().getName())) { + events.add(SimpleConditionEvent.violated(item, + item.getDescription() + " should only inject Environment")); + } } } @@ -232,6 +241,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"); + } + private List noClassesShouldCallObjectsRequireNonNull() { return List.of( ArchRuleDefinition.noClasses() @@ -277,8 +298,7 @@ final FileTree getInputClasses() { @Input // The rules themselves can't be an input as they aren't serializable so we use - // their - // descriptions instead + // their descriptions instead abstract ListProperty getRuleDescriptions(); } 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..d0c28ccafdb1 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. @@ -16,6 +16,8 @@ package org.springframework.boot.build.artifacts; +import java.util.Locale; + import org.gradle.api.Project; /** @@ -26,24 +28,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(Locale.ROOT); } public String getDownloadRepo() { @@ -51,24 +47,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/DocumentAutoConfigurationClasses.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java index 0ed06722930f..4d34cba44d91 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 @@ -83,9 +83,9 @@ private void writeTable(AutoConfiguration autoConfigurationClasses) throws IOExc 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 0d6503f252dc..5d36b3f5ed60 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; @@ -67,11 +68,14 @@ import org.springframework.boot.build.mavenplugin.MavenExec; import org.springframework.boot.build.properties.BuildProperties; 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 { @@ -120,7 +124,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() { @@ -248,6 +253,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; @@ -284,6 +293,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; @@ -364,9 +384,8 @@ public void setPlugins(List plugins) { } public Object methodMissing(String name, Object args) { - if (args instanceof Object[] && ((Object[]) args).length == 1) { - Object arg = ((Object[]) args)[0]; - if (arg instanceof Closure closure) { + if (args instanceof Object[] argsArray && argsArray.length == 1) { + if (argsArray[0] instanceof Closure closure) { ModuleHandler moduleHandler = new ModuleHandler(); closure.setResolveStrategy(Closure.DELEGATE_FIRST); closure.setDelegate(moduleHandler); @@ -436,6 +455,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 a02a460fee4a..86ec205bf238 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 @@ -65,8 +65,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) { @@ -290,9 +290,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/CheckLinks.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java new file mode 100644 index 000000000000..77bcfebf32c5 --- /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.printf("[%3d] %s - %s (%s)%n", 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 6657796cbf89..1f8e1d4f9bca 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,6 +25,8 @@ 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; @@ -60,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}. @@ -72,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; @@ -87,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. */ @@ -193,6 +219,53 @@ 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; + } + + public String forMajorMinorGeneration() { + String[] parts = parts(); + String result = parts[0] + "." + parts[1] + ".x"; + if (toString().endsWith("SNAPSHOT")) { + result += "-SNAPSHOT"; + } + return result; + } + + private String[] parts() { + return toString().split("[.-]"); + } + } /** diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/ManagedDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/ManagedDependencies.java index c9066aa053bd..cb6ca9581c3e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/ManagedDependencies.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/ManagedDependencies.java @@ -99,7 +99,7 @@ static ManagedDependencies ofBom(File bom) { static String asId(String groupId, String artifactId, String version, String classifier) { String id = groupId + ":" + artifactId + ":" + version; - if (classifier != null && classifier.length() > 0) { + if (classifier != null && !classifier.isEmpty()) { id = id + ":" + classifier; } return id; 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 2886e78d7115..4a4635e2ddfa 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 @@ -170,8 +170,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; } @@ -194,7 +195,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(); @@ -257,9 +258,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/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/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/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 90d25027c9e2..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 @@ -47,7 +47,7 @@ public DocumentDevtoolsPropertyDefaults() { this.devtools = getProject().getConfigurations().create("devtools"); 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"); 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 c48a4470d3f1..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 @@ -71,7 +71,7 @@ private void writeOverview(Plugin plugin) throws IOException { 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(); } @@ -82,11 +82,9 @@ 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(getOutputDir().getAsFile().get(), mojo.getGoal() + ".adoc")))) { - String sectionId = goalSectionId(mojo); - writer.println(); - writer.println(); + 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()); @@ -94,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) { + 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) { @@ -134,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) { @@ -216,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/starters/DocumentStarters.java b/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java index b3e1f554ce01..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 @@ -121,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/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..992d303bef70 --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties @@ -0,0 +1,85 @@ +# === 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-cyclonedx-docs-gradle-plugin=https://github.com/CycloneDX/cyclonedx-gradle-plugin +url-cyclonedx-docs-maven-plugin=https://github.com/CycloneDX/cyclonedx-maven-plugin +url-download-liberica-nik=https://bell-sw.com/pages/downloads/native-image-kit/#/nik-22-17 +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-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-javadoc=https://docs.spring.io/spring-data/cassandra/docs/{version-spring-data-cassandra-javadoc}/api +url-spring-data-cassandra-site=https://spring.io/projects/spring-data-cassandra +url-spring-data-cassandra-docs=https://docs.spring.io/spring-data/cassandra/reference/{version-spring-data-cassandra-docs} +url-spring-data-commons-javadoc=https://docs.spring.io/spring-data/commons/docs/{version-spring-data-commons-javadoc}/api +url-spring-data-couchbase-docs=https://docs.spring.io/spring-data/couchbase/reference/{version-spring-data-couchbase-docs} +url-spring-data-couchbase-site=https://spring.io/projects/spring-data-couchbase +url-spring-data-couchbase-javadoc=https://docs.spring.io/spring-data/couchbase/docs/{version-spring-data-couchbase-javadoc}/api +url-spring-data-elasticsearch-javadoc=https://docs.spring.io/spring-data/elasticsearch/docs/{version-spring-data-elasticsearch-javadoc}/api +url-spring-data-elasticsearch-docs=https://docs.spring.io/spring-data/elasticsearch/reference/{version-spring-data-elasticsearch-docs} +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-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-docs} +url-spring-data-jpa-javadoc=https://docs.spring.io/spring-data/jpa/docs/{version-spring-data-jpa-javadoc}/api +url-spring-data-jpa-site=https://spring.io/projects/spring-data-jpa +url-spring-data-jpa-docs=https://docs.spring.io/spring-data/jpa/reference/{version-spring-data-jpa-docs} +url-spring-data-ldap-site=https://spring.io/projects/spring-data-ldap +url-spring-data-ldap-docs=https://docs.spring.io/spring-data/ldap/reference/{version-spring-data-ldap-docs} +url-spring-data-mongodb-javadoc=https://docs.spring.io/spring-data/mongodb/docs/{version-spring-data-mongodb-javadoc}/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-docs} +url-spring-data-neo4j-javadoc=https://docs.spring.io/spring-data/neo4j/docs/{version-spring-data-neo4j-javadoc}/api +url-spring-data-neo4j-docs=https://docs.spring.io/spring-data/neo4j/reference/{version-spring-data-neo4j-docs} +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-javadoc}/api +url-spring-data-r2dbc-docs=https://docs.spring.io/spring-data/relational/reference/{version-spring-data-r2dbc-docs} +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-javadoc}/api +url-spring-data-site=https://spring.io/projects/spring-data +url-jackson-annotations=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/{version-jackson-annotations} +url-jackson-core=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/{version-jackson-core} +url-jackson-databind=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson-databind} + +# === 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..27da2d48d362 --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml @@ -0,0 +1,26 @@ +antora: + extensions: +site: + title: Spring Boot +content: + sources: [] +asciidoc: + sourcemap: true + attributes: + chomp: all + hide-uri-scheme: '@' + javadoc-location: xref:api:java/ + 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/javadoc-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/antora/AntoraAsciidocAttributesTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java new file mode 100644 index 000000000000..6df1bd64897b --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java @@ -0,0 +1,258 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.springframework.boot.build.properties.BuildType; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AntoraAsciidocAttributes}. + * + * @author Phillip Webb + * @author Stephane Nicoll + */ +class AntoraAsciidocAttributesTests { + + @Test + void buildTypeWhenOpenSource() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("build-type", "opensource"); + } + + @Test + void buildTypeWhenCommercial() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.COMMERCIAL, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("build-type", "commercial"); + } + + @Test + void githubRefWhenReleasedVersionIsTag() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("github-ref", "v1.2.3"); + } + + @Test + void githubRefWhenLatestSnapshotVersionIsMainBranch() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, + BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("github-ref", "main"); + } + + @Test + void githubRefWhenOlderSnapshotVersionIsBranch() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", false, + BuildType.OPEN_SOURCE, 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, + BuildType.OPEN_SOURCE, 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, + BuildType.OPEN_SOURCE, List.of(library), mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("version-spring-framework", "1.2.3"); + } + + @Test + void versionReferenceFromSpringDataDependencyReleaseVersion() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions("3.2.5"), null); + assertThat(attributes.get()).containsEntry("version-spring-data-mongodb-docs", "3.2"); + assertThat(attributes.get()).containsEntry("version-spring-data-mongodb-javadoc", "3.2.x"); + } + + @Test + void versionReferenceFromSpringDataDependencySnapshotVersion() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions("3.2.0-SNAPSHOT"), null); + assertThat(attributes.get()).containsEntry("version-spring-data-mongodb-docs", "3.2-SNAPSHOT"); + assertThat(attributes.get()).containsEntry("version-spring-data-mongodb-javadoc", "3.2.x"); + } + + @Test + void versionNativeBuildTools() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, 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, BuildType.OPEN_SOURCE, 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, BuildType.OPEN_SOURCE, + 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, + BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/snapshot"); + } + + @Test + void artifactReleaseTypeWhenOpenSourceRelease() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("artifact-release-type", "release"); + assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-release"); + } + + @Test + void artifactReleaseTypeWhenOpenSourceMilestone() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, + null, mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("artifact-release-type", "milestone"); + assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-milestone"); + } + + @Test + void artifactReleaseTypeWhenOpenSourceSnapshot() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, + BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("artifact-release-type", "snapshot"); + assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-snapshot"); + } + + @Test + void artifactReleaseTypeWhenCommercialRelease() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.COMMERCIAL, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("artifact-release-type", "release"); + assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-release"); + } + + @Test + void artifactReleaseTypeWhenCommercialMilestone() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.COMMERCIAL, null, + mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("artifact-release-type", "milestone"); + assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-milestone"); + } + + @Test + void artifactReleaseTypeWhenCommercialSnapshot() { + AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.COMMERCIAL, + null, mockDependencyVersions(), null); + assertThat(attributes.get()).containsEntry("artifact-release-type", "snapshot"); + assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-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, + BuildType.OPEN_SOURCE, 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, BuildType.OPEN_SOURCE, + 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() { + return mockDependencyVersions("1.2.3"); + } + + private Map mockDependencyVersions(String version) { + Map versions = new LinkedHashMap<>(); + addMockSpringDataVersion(versions, "spring-data-commons", version); + addMockSpringDataVersion(versions, "spring-data-cassandra", version); + addMockSpringDataVersion(versions, "spring-data-couchbase", version); + addMockSpringDataVersion(versions, "spring-data-elasticsearch", version); + addMockSpringDataVersion(versions, "spring-data-jdbc", version); + addMockSpringDataVersion(versions, "spring-data-jpa", version); + addMockSpringDataVersion(versions, "spring-data-mongodb", version); + addMockSpringDataVersion(versions, "spring-data-neo4j", version); + addMockSpringDataVersion(versions, "spring-data-r2dbc", version); + addMockSpringDataVersion(versions, "spring-data-rest-core", version); + addMockSpringDataVersion(versions, "spring-data-ldap", version); + addMockJacksonVersion(versions, "jackson-annotations", version); + addMockJacksonVersion(versions, "jackson-core", version); + addMockJacksonVersion(versions, "jackson-databind", version); + return versions; + } + + private void addMockSpringDataVersion(Map versions, String artifactId, String version) { + versions.put("org.springframework.data:" + artifactId, version); + } + + private void addMockJacksonVersion(Map versions, String artifactId, String version) { + versions.put("com.fasterxml.jackson.core:" + artifactId, version); + } + +} 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..c996fdcb9981 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.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.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('\\', '/')); + } + + @Test + void writePlaybookWhenHasJavadocExcludeGeneratesExpectedContent() throws Exception { + writePlaybookYml((task) -> { + task.getXrefStubs().addAll("appendix:.*", "api:.*", "reference:.*"); + task.getAlwaysInclude().set(Map.of("name", "test", "classifier", "local-aggregate-content")); + task.getExcludeJavadocExtension().set(true); + }); + String actual = Files.readString(this.temp.toPath() + .resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml")); + assertThat(actual).doesNotContain("javadoc-extension"); + } + + 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 2745dc01aa7d..ff419b347abc 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 @@ -36,6 +36,7 @@ * Tests for {@link ArchitectureCheck}. * * @author Andy Wilkinson + * @author Scott Frederick * @author Ivan Malutin */ class ArchitectureCheckTests { @@ -127,6 +128,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(); + }); + } + @Test void whenClassDoesNotCallObjectsRequireNonNullTaskSucceedsAndWritesAnEmptyReport() throws Exception { prepareTask("objects/noRequireNonNull", (architectureCheck) -> { @@ -202,7 +219,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..95124e1fc083 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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"); + } + + @Test + void toMajorMinorGenerationWithRelease() { + LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); + assertThat(version.forMajorMinorGeneration()).isEqualTo("1.2.x"); + } + + @Test + void toMajorMinorGenerationWithSnapshot() { + LibraryVersion version = new LibraryVersion(DependencyVersion.parse("2.0.0-SNAPSHOT")); + assertThat(version.forMajorMinorGeneration()).isEqualTo("2.0.x-SNAPSHOT"); + } + +} 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..340924854f52 --- /dev/null +++ b/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml @@ -0,0 +1,50 @@ +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: '@' + javadoc-location: xref:api:java/ + 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/javadoc-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 0e060372d33f..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.2.x" - label="Spring Boot 3.2.x"> + name="spring.boot.3.4.x" + label="Spring Boot 3.4.x"> { - String formatted = version.split("\\.").take(2).join('.') - return version.endsWith("-SNAPSHOT") ? formatted + "-SNAPSHOT" : formatted - } - def integrationVersion = versionConstraints["org.springframework.integration:spring-integration-core"] - String integrationDocs = String.format("https://docs.spring.io/spring-integration/reference/%s", toAntoraVersion(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(layout.buildDirectory.dir("generated-snippets")).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("generatedSnippets") + inputs.dir(layout.buildDirectory.dir("generated-snippets")) + .withPathSensitivity(PathSensitivity.RELATIVE) + .withPropertyName("generatedSnippets") + destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content') + archiveClassifier = "actuator-rest-api-aggregate-content" + from(layout.buildDirectory.dir("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 dad34a128c32..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.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/audit/AuditEventsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfiguration.java index 774b662d1df8..53f14009c809 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/audit/AuditEventsEndpointAutoConfiguration.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,7 +34,7 @@ * @since 2.0.0 */ @AutoConfiguration(after = AuditAutoConfiguration.class) -@ConditionalOnAvailableEndpoint(endpoint = AuditEventsEndpoint.class) +@ConditionalOnAvailableEndpoint(AuditEventsEndpoint.class) public class AuditEventsEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java index 591fce793600..82b6d29a610a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.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,6 +91,9 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM if (CloudPlatform.getActive(environment) == CloudPlatform.KUBERNETES) { return ConditionOutcome.match(message.because("running on Kubernetes")); } + if (CloudPlatform.getActive(environment) == CloudPlatform.CLOUD_FOUNDRY) { + return ConditionOutcome.match(message.because("running on Cloud Foundry")); + } return ConditionOutcome.noMatch(message.because("not running on a supported cloud platform")); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/beans/BeansEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/beans/BeansEndpointAutoConfiguration.java index 806a62858330..d3919e8eed74 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/beans/BeansEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/beans/BeansEndpointAutoConfiguration.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,7 +31,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = BeansEndpoint.class) +@ConditionalOnAvailableEndpoint(BeansEndpoint.class) public class BeansEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cache/CachesEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cache/CachesEndpointAutoConfiguration.java index a427adfdbc7b..aa77bc8fdf22 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cache/CachesEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cache/CachesEndpointAutoConfiguration.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,7 +40,7 @@ */ @AutoConfiguration(after = CacheAutoConfiguration.class) @ConditionalOnClass(CacheManager.class) -@ConditionalOnAvailableEndpoint(endpoint = CachesEndpoint.class) +@ConditionalOnAvailableEndpoint(CachesEndpoint.class) public class CachesEndpointAutoConfiguration { @Bean @@ -52,7 +52,7 @@ public CachesEndpoint cachesEndpoint(Map cacheManagers) { @Bean @ConditionalOnMissingBean @ConditionalOnBean(CachesEndpoint.class) - @ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) + @ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB) public CachesEndpointWebExtension cachesEndpointWebExtension(CachesEndpoint cachesEndpoint) { return new CachesEndpointWebExtension(cachesEndpoint); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointExposureOutcomeContributor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointExposureOutcomeContributor.java new file mode 100644 index 000000000000..b0c06ad06f8a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointExposureOutcomeContributor.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.actuate.autoconfigure.cloudfoundry; + +import java.util.Set; + +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.EndpointExposureOutcomeContributor; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.core.env.Environment; + +/** + * {@link EndpointExposureOutcomeContributor} to expose {@link EndpointExposure#WEB web} + * endpoints for Cloud Foundry. + * + * @author Phillip Webb + */ +class CloudFoundryEndpointExposureOutcomeContributor implements EndpointExposureOutcomeContributor { + + private static final String PROPERTY = "management.endpoints.cloud-foundry.exposure"; + + private final IncludeExcludeEndpointFilter filter; + + CloudFoundryEndpointExposureOutcomeContributor(Environment environment) { + this.filter = (!CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) ? null + : new IncludeExcludeEndpointFilter<>(ExposableEndpoint.class, environment, PROPERTY, "*"); + } + + @Override + public ConditionOutcome getExposureOutcome(EndpointId endpointId, Set exposures, + Builder message) { + if (exposures.contains(EndpointExposure.WEB) && this.filter != null && this.filter.match(endpointId)) { + return ConditionOutcome.match(message.because("marked as exposed by a '" + PROPERTY + "' property")); + } + return null; + } + +} 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..27763edec389 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. @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.springframework.aot.hint.MemberCategory; @@ -24,11 +25,13 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebEndpointDiscoverer.CloudFoundryWebEndpointDiscovererRuntimeHints; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.OperationFilter; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; +import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.actuate.health.HealthEndpoint; @@ -53,23 +56,44 @@ public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer { * @param endpointMediaTypes the endpoint media types * @param endpointPathMappers the endpoint path mappers * @param invokerAdvisors invoker advisors to apply - * @param filters filters to apply + * @param endpointFilters endpoint filters to apply + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #CloudFoundryWebEndpointDiscoverer(ApplicationContext, ParameterValueMapper, EndpointMediaTypes, List, Collection, Collection, Collection)} */ + @Deprecated(since = "3.4.0", forRemoval = true) public CloudFoundryWebEndpointDiscoverer(ApplicationContext applicationContext, ParameterValueMapper parameterValueMapper, EndpointMediaTypes endpointMediaTypes, List endpointPathMappers, Collection invokerAdvisors, - Collection> filters) { - super(applicationContext, parameterValueMapper, endpointMediaTypes, endpointPathMappers, invokerAdvisors, - filters); + Collection> endpointFilters) { + this(applicationContext, parameterValueMapper, endpointMediaTypes, endpointPathMappers, invokerAdvisors, + endpointFilters, Collections.emptyList()); + } + + /** + * Create a new {@link WebEndpointDiscoverer} instance. + * @param applicationContext the source application context + * @param parameterValueMapper the parameter value mapper + * @param endpointMediaTypes the endpoint media types + * @param endpointPathMappers the endpoint path mappers + * @param invokerAdvisors invoker advisors to apply + * @param endpointFilters endpoint filters to apply + * @param operationFilters operation filters to apply + * @since 3.4.0 + */ + public CloudFoundryWebEndpointDiscoverer(ApplicationContext applicationContext, + ParameterValueMapper parameterValueMapper, EndpointMediaTypes endpointMediaTypes, + List endpointPathMappers, Collection invokerAdvisors, + Collection> endpointFilters, + Collection> operationFilters) { + super(applicationContext, parameterValueMapper, endpointMediaTypes, endpointPathMappers, null, invokerAdvisors, + endpointFilters, operationFilters); } @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/ReactiveCloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java index a9e011b9529d..f19dd0d744b5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.actuate.info.GitInfoContributor; @@ -107,12 +106,15 @@ public CloudFoundryInfoEndpointWebExtension cloudFoundryInfoEndpointWebExtension } @Bean + @SuppressWarnings("removal") public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping( ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, - WebClient.Builder webClientBuilder, ControllerEndpointsSupplier controllerEndpointsSupplier, + WebClient.Builder webClientBuilder, + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, ApplicationContext applicationContext) { CloudFoundryWebEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebEndpointDiscoverer(applicationContext, - parameterMapper, endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList()); + parameterMapper, endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList()); CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(webClientBuilder, applicationContext.getEnvironment()); Collection webEndpoints = endpointDiscoverer.getEndpoints(); 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..f4c234712dae 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. @@ -34,8 +34,6 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.info.GitInfoContributor; @@ -111,12 +109,16 @@ public CloudFoundryInfoEndpointWebExtension cloudFoundryInfoEndpointWebExtension } @Bean + @SuppressWarnings("removal") public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, - RestTemplateBuilder restTemplateBuilder, ServletEndpointsSupplier servletEndpointsSupplier, - ControllerEndpointsSupplier controllerEndpointsSupplier, ApplicationContext applicationContext) { + RestTemplateBuilder restTemplateBuilder, + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, + ApplicationContext applicationContext) { CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(applicationContext, - parameterMapper, endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList()); + parameterMapper, endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList()); CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(restTemplateBuilder, applicationContext.getEnvironment()); Collection webEndpoints = discoverer.getEndpoints(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java index 35795f2f6ac6..bb0e78a86095 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.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,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = ConditionsReportEndpoint.class) +@ConditionalOnAvailableEndpoint(ConditionsReportEndpoint.class) public class ConditionsReportEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/ShutdownEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/ShutdownEndpointAutoConfiguration.java index fac0d585bd64..9d381b5f6e12 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/ShutdownEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/ShutdownEndpointAutoConfiguration.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. @@ -30,7 +30,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = ShutdownEndpoint.class) +@ConditionalOnAvailableEndpoint(ShutdownEndpoint.class) public class ShutdownEndpointAutoConfiguration { @Bean(destroyMethod = "") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java index 29c9ce665b74..1ff4fd4e23e0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfiguration.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,7 +39,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = ConfigurationPropertiesReportEndpoint.class) +@ConditionalOnAvailableEndpoint(ConfigurationPropertiesReportEndpoint.class) @EnableConfigurationProperties(ConfigurationPropertiesReportEndpointProperties.class) public class ConfigurationPropertiesReportEndpointAutoConfiguration { @@ -55,7 +55,7 @@ public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoi @Bean @ConditionalOnMissingBean @ConditionalOnBean(ConfigurationPropertiesReportEndpoint.class) - @ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) + @ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB) public ConfigurationPropertiesReportEndpointWebExtension configurationPropertiesReportEndpointWebExtension( ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint, ConfigurationPropertiesReportEndpointProperties properties) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java index 6448366a8074..e2e7bbd13a57 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.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.List; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointConverter; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -73,4 +74,10 @@ public CachingOperationInvokerAdvisor endpointCachingOperationInvokerAdvisor(Env return new CachingOperationInvokerAdvisor(new EndpointIdTimeToLivePropertyFunction(environment)); } + @Bean + @ConditionalOnMissingBean(EndpointAccessResolver.class) + PropertiesEndpointAccessResolver propertiesEndpointAccessResolver(Environment environment) { + return new PropertiesEndpointAccessResolver(environment); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/PropertiesEndpointAccessResolver.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/PropertiesEndpointAccessResolver.java new file mode 100644 index 000000000000..0f05abef2615 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/PropertiesEndpointAccessResolver.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; +import org.springframework.core.env.PropertyResolver; + +/** + * {@link EndpointAccessResolver} that resolves the permitted level of access to an + * endpoint using the following properties: + *
    + *
  1. {@code management.endpoint..access} or {@code management.endpoint..enabled} + * (deprecated) + *
  2. {@code management.endpoints.access.default} or + * {@code management.endpoints.enabled-by-default} (deprecated) + *
+ * The resulting access is capped using {@code management.endpoints.access.max-permitted}. + * + * @author Andy Wilkinson + * @since 3.4.0 + */ +public class PropertiesEndpointAccessResolver implements EndpointAccessResolver { + + private static final String DEFAULT_ACCESS_KEY = "management.endpoints.access.default"; + + private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default"; + + private final PropertyResolver properties; + + private final Access endpointsDefaultAccess; + + private final Access maxPermittedAccess; + + private final Map accessCache = new ConcurrentHashMap<>(); + + public PropertiesEndpointAccessResolver(PropertyResolver properties) { + this.properties = properties; + this.endpointsDefaultAccess = determineDefaultAccess(properties); + this.maxPermittedAccess = properties.getProperty("management.endpoints.access.max-permitted", Access.class, + Access.UNRESTRICTED); + } + + private static Access determineDefaultAccess(PropertyResolver properties) { + Access defaultAccess = properties.getProperty(DEFAULT_ACCESS_KEY, Access.class); + Boolean endpointsEnabledByDefault = properties.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put(DEFAULT_ACCESS_KEY, defaultAccess); + entries.put(ENABLED_BY_DEFAULT_KEY, endpointsEnabledByDefault); + }); + if (defaultAccess != null) { + return defaultAccess; + } + if (endpointsEnabledByDefault != null) { + return endpointsEnabledByDefault ? org.springframework.boot.actuate.endpoint.Access.UNRESTRICTED + : org.springframework.boot.actuate.endpoint.Access.NONE; + } + return null; + } + + @Override + public Access accessFor(EndpointId endpointId, Access defaultAccess) { + return this.accessCache.computeIfAbsent(endpointId, + (key) -> resolveAccess(endpointId, defaultAccess).cap(this.maxPermittedAccess)); + } + + private Access resolveAccess(EndpointId endpointId, Access defaultAccess) { + String accessKey = "management.endpoint.%s.access".formatted(endpointId); + String enabledKey = "management.endpoint.%s.enabled".formatted(endpointId); + Access access = this.properties.getProperty(accessKey, Access.class); + Boolean enabled = this.properties.getProperty(enabledKey, Boolean.class); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put(accessKey, access); + entries.put(enabledKey, enabled); + }); + if (access != null) { + return access; + } + if (enabled != null) { + return (enabled) ? Access.UNRESTRICTED : Access.NONE; + } + return (this.endpointsDefaultAccess != null) ? this.endpointsDefaultAccess : defaultAccess; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.java index 2773446c3e12..0e83b125aa92 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.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. @@ -26,20 +26,24 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.context.annotation.Conditional; +import org.springframework.core.annotation.AliasFor; import org.springframework.core.env.Environment; /** * {@link Conditional @Conditional} that checks whether an endpoint is available. An * endpoint is considered available if it is both enabled and exposed on the specified - * technologies. Matches enablement according to the endpoints specific - * {@link Environment} property, falling back to - * {@code management.endpoints.enabled-by-default} or failing that - * {@link Endpoint#enableByDefault()}. Matches exposure according to any of the - * {@code management.endpoints.web.exposure.} or - * {@code management.endpoints.jmx.exposure.} specific properties or failing that to - * whether the application runs on - * {@link org.springframework.boot.cloud.CloudPlatform#CLOUD_FOUNDRY}. Both those - * conditions should match for the endpoint to be considered available. + * technologies. + *

+ * Matches enablement according to the endpoints specific {@link Environment} property, + * falling back to {@code management.endpoints.enabled-by-default} or failing that + * {@link Endpoint#enableByDefault()}. + *

+ * Matches exposure according to any of the {@code management.endpoints.web.exposure.} + * or {@code management.endpoints.jmx.exposure.} specific properties or failing that + * to whether any {@link EndpointExposureOutcomeContributor} exposes the endpoint. + *

+ * Both enablement and exposure conditions should match for the endpoint to be considered + * available. *

* When placed on a {@code @Bean} method, the endpoint defaults to the return type of the * factory method: @@ -97,6 +101,8 @@ * * @author Brian Clozel * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb * @since 2.2.0 * @see Endpoint */ @@ -106,12 +112,21 @@ @Conditional(OnAvailableEndpointCondition.class) public @interface ConditionalOnAvailableEndpoint { + /** + * Alias for {@link #endpoint()}. + * @return the endpoint type to check + * @since 3.4.0 + */ + @AliasFor(attribute = "endpoint") + Class value() default Void.class; + /** * The endpoint type that should be checked. Inferred when the return type of the * {@code @Bean} method is either an {@link Endpoint @Endpoint} or an * {@link EndpointExtension @EndpointExtension}. * @return the endpoint type to check */ + @AliasFor(attribute = "value") Class endpoint() default Void.class; /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/EndpointExposureOutcomeContributor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/EndpointExposureOutcomeContributor.java new file mode 100644 index 000000000000..4b74aa29ee31 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/EndpointExposureOutcomeContributor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.condition; + +import java.util.Set; + +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.core.env.Environment; + +/** + * Contributor loaded from the {@code spring.factories} file and used by + * {@link ConditionalOnAvailableEndpoint @ConditionalOnAvailableEndpoint} to determine if + * an endpoint is exposed. If any contributor returns a {@link ConditionOutcome#isMatch() + * matching} {@link ConditionOutcome} then the endpoint is considered exposed. + *

+ * Implementations may declare a constructor that accepts an {@link Environment} argument. + * + * @author Andy Wilkinson + * @author Phillip Webb + * @since 3.4.0 + */ +public interface EndpointExposureOutcomeContributor { + + /** + * Return if the given endpoint is exposed for the given set of exposure technologies. + * @param endpointId the endpoint ID + * @param exposures the exposure technologies to check + * @param message the condition message builder + * @return a {@link ConditionOutcome#isMatch() matching} {@link ConditionOutcome} if + * the endpoint is exposed or {@code null} if the contributor should not apply + */ + ConditionOutcome getExposureOutcome(EndpointId endpointId, Set exposures, + ConditionMessage.Builder message); + +} 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 24e75a9005db..4d3595da8c98 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 @@ -17,30 +17,35 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition; import java.util.Arrays; +import java.util.Collection; import java.util.EnumSet; -import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.Set; +import org.springframework.boot.actuate.autoconfigure.endpoint.PropertiesEndpointAccessResolver; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.cloud.CloudPlatform; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.annotation.MergedAnnotation; 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.core.io.support.SpringFactoriesLoader.ArgumentResolver; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.util.Assert; @@ -48,22 +53,21 @@ import org.springframework.util.ConcurrentReferenceHashMap; /** - * A condition that checks if an endpoint is available (i.e. enabled and exposed). + * A condition that checks if an endpoint is available (i.e. accessible and exposed). * * @author Brian Clozel * @author Stephane Nicoll * @author Phillip Webb + * @author Andy Wilkinson * @see ConditionalOnAvailableEndpoint */ class OnAvailableEndpointCondition extends SpringBootCondition { private static final String JMX_ENABLED_KEY = "spring.jmx.enabled"; - private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default"; + private static final Map accessResolversCache = new ConcurrentReferenceHashMap<>(); - private static final Map> exposureFiltersCache = new ConcurrentReferenceHashMap<>(); - - private static final ConcurrentReferenceHashMap> enabledByDefaultCache = new ConcurrentReferenceHashMap<>(); + private static final Map> exposureOutcomeContributorsCache = new ConcurrentReferenceHashMap<>(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { @@ -110,93 +114,106 @@ private ConditionOutcome getMatchOutcome(Environment environment, MergedAnnotation endpointAnnotation) { ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnAvailableEndpoint.class); EndpointId endpointId = EndpointId.of(environment, endpointAnnotation.getString("id")); - ConditionOutcome enablementOutcome = getEnablementOutcome(environment, endpointAnnotation, endpointId, message); - if (!enablementOutcome.isMatch()) { - return enablementOutcome; - } - Set exposuresToCheck = getExposuresToCheck(conditionAnnotation); - Set exposureFilters = getExposureFilters(environment); - for (ExposureFilter exposureFilter : exposureFilters) { - if (exposuresToCheck.contains(exposureFilter.getExposure()) && exposureFilter.isExposed(endpointId)) { - return ConditionOutcome.match(message.because("marked as exposed by a 'management.endpoints." - + exposureFilter.getExposure().name().toLowerCase(Locale.ROOT) + ".exposure' property")); - } + ConditionOutcome accessOutcome = getAccessOutcome(environment, endpointAnnotation, endpointId, message); + if (!accessOutcome.isMatch()) { + return accessOutcome; } - return ConditionOutcome.noMatch(message.because("no 'management.endpoints' property marked it as exposed")); + ConditionOutcome exposureOutcome = getExposureOutcome(environment, conditionAnnotation, endpointAnnotation, + endpointId, message); + return (exposureOutcome != null) ? exposureOutcome : ConditionOutcome.noMatch(message.because("not exposed")); } - private ConditionOutcome getEnablementOutcome(Environment environment, - MergedAnnotation endpointAnnotation, EndpointId endpointId, ConditionMessage.Builder message) { - String key = "management.endpoint." + endpointId.toLowerCaseString() + ".enabled"; - Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class); - if (userDefinedEnabled != null) { - return new ConditionOutcome(userDefinedEnabled, - message.because("found property " + key + " with value " + userDefinedEnabled)); - } - Boolean userDefinedDefault = isEnabledByDefault(environment); - if (userDefinedDefault != null) { - return new ConditionOutcome(userDefinedDefault, message - .because("no property " + key + " found so using user defined default from " + ENABLED_BY_DEFAULT_KEY)); + private ConditionOutcome getAccessOutcome(Environment environment, MergedAnnotation endpointAnnotation, + EndpointId endpointId, ConditionMessage.Builder message) { + Access defaultAccess = endpointAnnotation.getEnum("defaultAccess", Access.class); + boolean enableByDefault = endpointAnnotation.getBoolean("enableByDefault"); + Access access = getAccess(environment, endpointId, (enableByDefault) ? defaultAccess : Access.NONE); + return new ConditionOutcome(access != Access.NONE, + message.because("the configured access for endpoint '%s' is %s".formatted(endpointId, access))); + } + + private Access getAccess(Environment environment, EndpointId endpointId, Access defaultAccess) { + return accessResolversCache.computeIfAbsent(environment, PropertiesEndpointAccessResolver::new) + .accessFor(endpointId, defaultAccess); + } + + private ConditionOutcome getExposureOutcome(Environment environment, + MergedAnnotation conditionAnnotation, + MergedAnnotation endpointAnnotation, EndpointId endpointId, Builder message) { + Set exposures = getExposures(conditionAnnotation); + Set outcomeContributors = getExposureOutcomeContributors(environment); + for (EndpointExposureOutcomeContributor outcomeContributor : outcomeContributors) { + ConditionOutcome outcome = outcomeContributor.getExposureOutcome(endpointId, exposures, message); + if (outcome != null && outcome.isMatch()) { + return outcome; + } } - boolean endpointDefault = endpointAnnotation.getBoolean("enableByDefault"); - return new ConditionOutcome(endpointDefault, - message.because("no property " + key + " found so using endpoint default of " + endpointDefault)); + return null; } - private Boolean isEnabledByDefault(Environment environment) { - Optional enabledByDefault = enabledByDefaultCache.computeIfAbsent(environment, - (ignore) -> Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class))); - return enabledByDefault.orElse(null); + private Set getExposures(MergedAnnotation conditionAnnotation) { + EndpointExposure[] exposures = conditionAnnotation.getEnumArray("exposure", EndpointExposure.class); + return replaceCloudFoundryExposure( + (exposures.length == 0) ? EnumSet.allOf(EndpointExposure.class) : Arrays.asList(exposures)); } - private Set getExposuresToCheck( - MergedAnnotation conditionAnnotation) { - EndpointExposure[] exposure = conditionAnnotation.getEnumArray("exposure", EndpointExposure.class); - return (exposure.length == 0) ? EnumSet.allOf(EndpointExposure.class) - : new LinkedHashSet<>(Arrays.asList(exposure)); + @SuppressWarnings("removal") + private Set replaceCloudFoundryExposure(Collection exposures) { + Set result = EnumSet.copyOf(exposures); + if (result.remove(EndpointExposure.CLOUD_FOUNDRY)) { + result.add(EndpointExposure.WEB); + } + return result; } - private Set getExposureFilters(Environment environment) { - Set exposureFilters = exposureFiltersCache.get(environment); - if (exposureFilters == null) { - exposureFilters = new HashSet<>(2); + private Set getExposureOutcomeContributors(Environment environment) { + Set contributors = exposureOutcomeContributorsCache.get(environment); + if (contributors == null) { + contributors = new LinkedHashSet<>(); + contributors.add(new StandardExposureOutcomeContributor(environment, EndpointExposure.WEB)); if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) { - exposureFilters.add(new ExposureFilter(environment, EndpointExposure.JMX)); - } - if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) { - exposureFilters.add(new ExposureFilter(environment, EndpointExposure.CLOUD_FOUNDRY)); + contributors.add(new StandardExposureOutcomeContributor(environment, EndpointExposure.JMX)); } - exposureFilters.add(new ExposureFilter(environment, EndpointExposure.WEB)); - exposureFiltersCache.put(environment, exposureFilters); + contributors.addAll(loadExposureOutcomeContributors(environment)); + exposureOutcomeContributorsCache.put(environment, contributors); } - return exposureFilters; + return contributors; } - static final class ExposureFilter extends IncludeExcludeEndpointFilter> { + private List loadExposureOutcomeContributors(Environment environment) { + ArgumentResolver argumentResolver = ArgumentResolver.of(Environment.class, environment); + return SpringFactoriesLoader.forDefaultResourceLocation() + .load(EndpointExposureOutcomeContributor.class, argumentResolver); + } + + /** + * Standard {@link EndpointExposureOutcomeContributor}. + */ + private static class StandardExposureOutcomeContributor implements EndpointExposureOutcomeContributor { private final EndpointExposure exposure; - @SuppressWarnings({ "unchecked", "rawtypes" }) - private ExposureFilter(Environment environment, EndpointExposure exposure) { - super((Class) ExposableEndpoint.class, environment, - "management.endpoints." + getCanonicalName(exposure) + ".exposure", exposure.getDefaultIncludes()); - this.exposure = exposure; + private final String property; - } + private final IncludeExcludeEndpointFilter filter; - private static String getCanonicalName(EndpointExposure exposure) { - if (EndpointExposure.CLOUD_FOUNDRY.equals(exposure)) { - return "cloud-foundry"; - } - return exposure.name().toLowerCase(Locale.ROOT); - } + StandardExposureOutcomeContributor(Environment environment, EndpointExposure exposure) { + this.exposure = exposure; + String name = exposure.name().toLowerCase(Locale.ROOT).replace('_', '-'); + this.property = "management.endpoints." + name + ".exposure"; + this.filter = new IncludeExcludeEndpointFilter<>(ExposableEndpoint.class, environment, this.property, + exposure.getDefaultIncludes()); - EndpointExposure getExposure() { - return this.exposure; } - boolean isExposed(EndpointId id) { - return super.match(id); + @Override + public ConditionOutcome getExposureOutcome(EndpointId endpointId, Set exposures, + ConditionMessage.Builder message) { + if (exposures.contains(this.exposure) && this.filter.match(endpointId)) { + return ConditionOutcome + .match(message.because("marked as exposed by a '" + this.property + "' property")); + } + return null; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/EndpointExposure.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/EndpointExposure.java index 9b29b7212999..967f72bdf637 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/EndpointExposure.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/EndpointExposure.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 @@ public enum EndpointExposure { /** * Exposed on Cloud Foundry over `/cloudfoundryapplication`. * @since 2.6.4 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of using + * {@link EndpointExposure#WEB} */ + @Deprecated(since = "3.4.0", forRemoval = true) CLOUD_FOUNDRY("*"); private final String[] defaultIncludes; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java index 472a8d700969..6273598c5d08 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.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,7 +25,9 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.OperationFilter; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -34,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.jmx.JacksonJmxOperationResponseMapper; import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter; import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.jmx.JmxOperation; import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper; import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -80,9 +83,11 @@ public JmxEndpointAutoConfiguration(ApplicationContext applicationContext, JmxEn @ConditionalOnMissingBean(JmxEndpointsSupplier.class) public JmxEndpointDiscoverer jmxAnnotationEndpointDiscoverer(ParameterValueMapper parameterValueMapper, ObjectProvider invokerAdvisors, - ObjectProvider> filters) { + ObjectProvider> endpointFilters, + ObjectProvider> operationFilters) { return new JmxEndpointDiscoverer(this.applicationContext, parameterValueMapper, - invokerAdvisors.orderedStream().toList(), filters.orderedStream().toList()); + invokerAdvisors.orderedStream().toList(), endpointFilters.orderedStream().toList(), + operationFilters.orderedStream().toList()); } @Bean @@ -116,4 +121,9 @@ static LazyInitializationExcludeFilter eagerlyInitializeJmxEndpointExporter() { return LazyInitializationExcludeFilter.forBeanTypes(JmxEndpointExporter.class); } + @Bean + OperationFilter jmxAccessPropertiesOperationFilter(EndpointAccessResolver endpointAccessResolver) { + return OperationFilter.byAccess(endpointAccessResolver); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.java index d5dde9984dc4..287b70f921f9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.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,34 +37,32 @@ public class CorsEndpointProperties { /** - * Comma-separated list of origins to allow. '*' allows all origins. When credentials - * are allowed, '*' cannot be used and origin patterns should be configured instead. - * When no allowed origins or allowed origin patterns are set, CORS support is - * disabled. + * List of origins to allow. '*' allows all origins. When credentials are allowed, '*' + * cannot be used and origin patterns should be configured instead. When no allowed + * origins or allowed origin patterns are set, CORS support is disabled. */ private List allowedOrigins = new ArrayList<>(); /** - * Comma-separated list of origin patterns to allow. Unlike allowed origins which only - * supports '*', origin patterns are more flexible (for example - * 'https://*.example.com') and can be used when credentials are allowed. When no - * allowed origin patterns or allowed origins are set, CORS support is disabled. + * List of origin patterns to allow. Unlike allowed origins which only supports '*', + * origin patterns are more flexible (for example 'https://*.example.com') and can be + * used when credentials are allowed. When no allowed origin patterns or allowed + * origins are set, CORS support is disabled. */ private List allowedOriginPatterns = new ArrayList<>(); /** - * Comma-separated list of methods to allow. '*' allows all methods. When not set, - * defaults to GET. + * List of methods to allow. '*' allows all methods. When not set, defaults to GET. */ private List allowedMethods = new ArrayList<>(); /** - * Comma-separated list of headers to allow in a request. '*' allows all headers. + * List of headers to allow in a request. '*' allows all headers. */ private List allowedHeaders = new ArrayList<>(); /** - * Comma-separated list of headers to include in a response. + * List of headers to include in a response. */ private List exposedHeaders = new ArrayList<>(); 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..a0f262f38313 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. @@ -20,9 +20,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; -import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; -import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; @@ -47,10 +45,12 @@ public class ServletEndpointManagementContextConfiguration { @Bean - public IncludeExcludeEndpointFilter servletExposeExcludePropertyEndpointFilter( + @SuppressWarnings("removal") + public IncludeExcludeEndpointFilter servletExposeExcludePropertyEndpointFilter( WebEndpointProperties properties) { WebEndpointProperties.Exposure exposure = properties.getExposure(); - return new IncludeExcludeEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(), + return new IncludeExcludeEndpointFilter<>( + org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude()); } @@ -59,10 +59,14 @@ public IncludeExcludeEndpointFilter servletExposeExclu public static class WebMvcServletEndpointManagementContextConfiguration { @Bean - public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, - ServletEndpointsSupplier servletEndpointsSupplier, DispatcherServletPath dispatcherServletPath) { - return new ServletEndpointRegistrar(dispatcherServletPath.getRelativePath(properties.getBasePath()), - servletEndpointsSupplier.getEndpoints()); + @SuppressWarnings({ "deprecation", "removal" }) + public org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar servletEndpointRegistrar( + WebEndpointProperties properties, + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, + DispatcherServletPath dispatcherServletPath, EndpointAccessResolver endpointAccessResolver) { + return new org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar( + dispatcherServletPath.getRelativePath(properties.getBasePath()), + servletEndpointsSupplier.getEndpoints(), endpointAccessResolver); } } @@ -73,10 +77,14 @@ public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties p public static class JerseyServletEndpointManagementContextConfiguration { @Bean - public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, - ServletEndpointsSupplier servletEndpointsSupplier, JerseyApplicationPath jerseyApplicationPath) { - return new ServletEndpointRegistrar(jerseyApplicationPath.getRelativePath(properties.getBasePath()), - servletEndpointsSupplier.getEndpoints()); + @SuppressWarnings({ "deprecation", "removal" }) + public org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar servletEndpointRegistrar( + WebEndpointProperties properties, + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, + JerseyApplicationPath jerseyApplicationPath, EndpointAccessResolver endpointAccessResolver) { + return new org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar( + jerseyApplicationPath.getRelativePath(properties.getBasePath()), + servletEndpointsSupplier.getEndpoints(), endpointAccessResolver); } } 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..0d37cb7af002 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. @@ -23,22 +23,20 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; +import org.springframework.boot.actuate.endpoint.OperationFilter; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; -import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; -import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -87,18 +85,24 @@ public EndpointMediaTypes endpointMediaTypes() { @ConditionalOnMissingBean(WebEndpointsSupplier.class) public WebEndpointDiscoverer webEndpointDiscoverer(ParameterValueMapper parameterValueMapper, EndpointMediaTypes endpointMediaTypes, ObjectProvider endpointPathMappers, + ObjectProvider additionalPathsMappers, ObjectProvider invokerAdvisors, - ObjectProvider> filters) { + ObjectProvider> endpointFilters, + ObjectProvider> operationFilters) { return new WebEndpointDiscoverer(this.applicationContext, parameterValueMapper, endpointMediaTypes, - endpointPathMappers.orderedStream().toList(), invokerAdvisors.orderedStream().toList(), - filters.orderedStream().toList()); + endpointPathMappers.orderedStream().toList(), additionalPathsMappers.orderedStream().toList(), + invokerAdvisors.orderedStream().toList(), endpointFilters.orderedStream().toList(), + operationFilters.orderedStream().toList()); } @Bean - @ConditionalOnMissingBean(ControllerEndpointsSupplier.class) - public ControllerEndpointDiscoverer controllerEndpointDiscoverer(ObjectProvider endpointPathMappers, - ObjectProvider>> filters) { - return new ControllerEndpointDiscoverer(this.applicationContext, endpointPathMappers.orderedStream().toList(), + @ConditionalOnMissingBean(org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier.class) + @SuppressWarnings({ "deprecation", "removal" }) + public org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer controllerEndpointDiscoverer( + ObjectProvider endpointPathMappers, + ObjectProvider>> filters) { + return new org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer( + this.applicationContext, endpointPathMappers.orderedStream().toList(), filters.getIfAvailable(Collections::emptyList)); } @@ -116,10 +120,17 @@ public IncludeExcludeEndpointFilter webExposeExcludeProper } @Bean - public IncludeExcludeEndpointFilter controllerExposeExcludePropertyEndpointFilter() { + @SuppressWarnings("removal") + public IncludeExcludeEndpointFilter controllerExposeExcludePropertyEndpointFilter() { WebEndpointProperties.Exposure exposure = this.properties.getExposure(); - return new IncludeExcludeEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(), - exposure.getExclude()); + return new IncludeExcludeEndpointFilter<>( + org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint.class, + exposure.getInclude(), exposure.getExclude()); + } + + @Bean + OperationFilter webAccessPropertiesOperationFilter(EndpointAccessResolver endpointAccessResolver) { + return OperationFilter.byAccess(endpointAccessResolver); } @Configuration(proxyBeanMethods = false) @@ -127,12 +138,13 @@ public IncludeExcludeEndpointFilter controllerExpos static class WebEndpointServletConfiguration { @Bean - @ConditionalOnMissingBean(ServletEndpointsSupplier.class) - ServletEndpointDiscoverer servletEndpointDiscoverer(ApplicationContext applicationContext, - ObjectProvider endpointPathMappers, - ObjectProvider> filters) { - return new ServletEndpointDiscoverer(applicationContext, endpointPathMappers.orderedStream().toList(), - filters.orderedStream().toList()); + @SuppressWarnings({ "deprecation", "removal" }) + @ConditionalOnMissingBean(org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier.class) + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer servletEndpointDiscoverer( + ApplicationContext applicationContext, ObjectProvider endpointPathMappers, + ObjectProvider> filters) { + return new org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer( + applicationContext, endpointPathMappers.orderedStream().toList(), filters.orderedStream().toList()); } } 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 dd7a8668ca4c..587c6ba0f3a2 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. @@ -45,11 +45,9 @@ import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; -import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory; import org.springframework.boot.actuate.endpoint.web.jersey.JerseyHealthEndpointAdditionalPathResourceFactory; import org.springframework.boot.actuate.health.HealthEndpoint; @@ -84,8 +82,10 @@ class JerseyWebEndpointManagementContextConfiguration { private static final EndpointId HEALTH_ENDPOINT_ID = EndpointId.of("health"); @Bean + @SuppressWarnings("removal") JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Environment environment, - WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, + WebEndpointsSupplier webEndpointsSupplier, + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, WebEndpointProperties webEndpointProperties) { String basePath = webEndpointProperties.getBasePath(); boolean shouldRegisterLinks = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath); @@ -100,12 +100,12 @@ JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Enviro JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar jerseyDifferentPortAdditionalHealthEndpointPathsResourcesRegistrar( WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups healthEndpointGroups) { Collection webEndpoints = webEndpointsSupplier.getEndpoints(); - ExposableWebEndpoint health = webEndpoints.stream() + ExposableWebEndpoint healthEndpoint = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HEALTH_ENDPOINT_ID)) .findFirst() - .orElseThrow( - () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HEALTH_ENDPOINT_ID))); - return new JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(health, healthEndpointGroups); + .orElse(null); + return new JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(healthEndpoint, + healthEndpointGroups); } @Bean @@ -124,11 +124,12 @@ 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; - private final ServletEndpointsSupplier servletEndpointsSupplier; + private final org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier; private final EndpointMediaTypes mediaTypes; @@ -137,8 +138,8 @@ static class JerseyWebEndpointsResourcesRegistrar implements ManagementContextRe private final boolean shouldRegisterLinks; JerseyWebEndpointsResourcesRegistrar(WebEndpointsSupplier webEndpointsSupplier, - ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, - String basePath, boolean shouldRegisterLinks) { + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, + EndpointMediaTypes endpointMediaTypes, String basePath, boolean shouldRegisterLinks) { this.webEndpointsSupplier = webEndpointsSupplier; this.servletEndpointsSupplier = servletEndpointsSupplier; this.mediaTypes = endpointMediaTypes; @@ -153,7 +154,8 @@ public void customize(ResourceConfig config) { private void register(ResourceConfig config) { Collection webEndpoints = this.webEndpointsSupplier.getEndpoints(); - Collection servletEndpoints = this.servletEndpointsSupplier.getEndpoints(); + Collection servletEndpoints = this.servletEndpointsSupplier + .getEndpoints(); EndpointLinksResolver linksResolver = getLinksResolver(webEndpoints, servletEndpoints); EndpointMapping mapping = new EndpointMapping(this.basePath); Collection endpointResources = new JerseyEndpointResourceFactory().createEndpointResources( @@ -162,7 +164,7 @@ private void register(ResourceConfig config) { } private EndpointLinksResolver getLinksResolver(Collection webEndpoints, - Collection servletEndpoints) { + Collection servletEndpoints) { List> endpoints = new ArrayList<>(webEndpoints.size() + servletEndpoints.size()); endpoints.addAll(webEndpoints); endpoints.addAll(servletEndpoints); @@ -178,19 +180,21 @@ private void register(Collection resources, ResourceConfig config) { class JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar implements ManagementContextResourceConfigCustomizer { - private final ExposableWebEndpoint endpoint; + private final ExposableWebEndpoint healthEndpoint; private final HealthEndpointGroups groups; - JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(ExposableWebEndpoint endpoint, + JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(ExposableWebEndpoint healthEndpoint, HealthEndpointGroups groups) { - this.endpoint = endpoint; + this.healthEndpoint = healthEndpoint; this.groups = groups; } @Override public void customize(ResourceConfig config) { - register(config); + if (this.healthEndpoint != null) { + register(config); + } } private void register(ResourceConfig config) { @@ -198,7 +202,7 @@ private void register(ResourceConfig config) { JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory( WebServerNamespace.MANAGEMENT, this.groups); Collection endpointResources = resourceFactory - .createEndpointResources(mapping, Collections.singletonList(this.endpoint)) + .createEndpointResources(mapping, Collections.singletonList(this.healthEndpoint)) .stream() .filter(Objects::nonNull) .toList(); 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 94ea4766f50f..d49bc60405a8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -46,9 +47,7 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.reactive.AdditionalHealthEndpointPathsWebFluxHandlerMapping; -import org.springframework.boot.actuate.endpoint.web.reactive.ControllerEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointGroups; @@ -89,10 +88,11 @@ public class WebFluxEndpointManagementContextConfiguration { @Bean @ConditionalOnMissingBean + @SuppressWarnings("removal") public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, - ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, - CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, - Environment environment) { + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, + EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, + WebEndpointProperties webEndpointProperties, Environment environment) { String basePath = webEndpointProperties.getBasePath(); EndpointMapping endpointMapping = new EndpointMapping(basePath); Collection endpoints = webEndpointsSupplier.getEndpoints(); @@ -117,23 +117,26 @@ private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Env public AdditionalHealthEndpointPathsWebFluxHandlerMapping managementHealthEndpointWebFluxHandlerMapping( WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) { Collection webEndpoints = webEndpointsSupplier.getEndpoints(); - ExposableWebEndpoint health = webEndpoints.stream() + ExposableWebEndpoint healthEndpoint = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .orElseThrow( - () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); - return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), health, + .orElse(null); + return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), healthEndpoint, groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT)); } @Bean @ConditionalOnMissingBean - public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping( - ControllerEndpointsSupplier controllerEndpointsSupplier, CorsEndpointProperties corsProperties, - WebEndpointProperties webEndpointProperties) { + @SuppressWarnings("removal") + @Deprecated(since = "3.3.5", forRemoval = true) + public org.springframework.boot.actuate.endpoint.web.reactive.ControllerEndpointHandlerMapping controllerEndpointHandlerMapping( + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, + CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, + EndpointAccessResolver endpointAccessResolver) { EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath()); - return new ControllerEndpointHandlerMapping(endpointMapping, controllerEndpointsSupplier.getEndpoints(), - corsProperties.toCorsConfiguration()); + return new org.springframework.boot.actuate.endpoint.web.reactive.ControllerEndpointHandlerMapping( + endpointMapping, controllerEndpointsSupplier.getEndpoints(), corsProperties.toCorsConfiguration(), + endpointAccessResolver); } @Bean 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 451e08b61396..e5a5e0bdf2e6 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. @@ -32,6 +32,7 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -42,10 +43,7 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.servlet.AdditionalHealthEndpointPathsWebMvcHandlerMapping; -import org.springframework.boot.actuate.endpoint.web.servlet.ControllerEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointGroups; @@ -82,8 +80,10 @@ public class WebMvcEndpointManagementContextConfiguration { @Bean @ConditionalOnMissingBean + @SuppressWarnings("removal") public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, - ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) { List> allEndpoints = new ArrayList<>(); @@ -112,23 +112,30 @@ private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProp public AdditionalHealthEndpointPathsWebMvcHandlerMapping managementHealthEndpointWebMvcHandlerMapping( WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) { Collection webEndpoints = webEndpointsSupplier.getEndpoints(); - ExposableWebEndpoint health = webEndpoints.stream() - .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) + ExposableWebEndpoint healthEndpoint = webEndpoints.stream() + .filter(this::isHealthEndpoint) .findFirst() - .orElseThrow( - () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); - return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(health, + .orElse(null); + return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(healthEndpoint, groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT)); } + private boolean isHealthEndpoint(ExposableWebEndpoint endpoint) { + return endpoint.getEndpointId().equals(HealthEndpoint.ID); + } + @Bean @ConditionalOnMissingBean - public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping( - ControllerEndpointsSupplier controllerEndpointsSupplier, CorsEndpointProperties corsProperties, - WebEndpointProperties webEndpointProperties) { + @SuppressWarnings("removal") + @Deprecated(since = "3.3.5", forRemoval = true) + public org.springframework.boot.actuate.endpoint.web.servlet.ControllerEndpointHandlerMapping controllerEndpointHandlerMapping( + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, + CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, + EndpointAccessResolver endpointAccessResolver) { EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath()); - return new ControllerEndpointHandlerMapping(endpointMapping, controllerEndpointsSupplier.getEndpoints(), - corsProperties.toCorsConfiguration()); + return new org.springframework.boot.actuate.endpoint.web.servlet.ControllerEndpointHandlerMapping( + endpointMapping, controllerEndpointsSupplier.getEndpoints(), corsProperties.toCorsConfiguration(), + endpointAccessResolver); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java index 42e8308833b8..3b8ffbf98352 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/env/EnvironmentEndpointAutoConfiguration.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. @@ -38,7 +38,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = EnvironmentEndpoint.class) +@ConditionalOnAvailableEndpoint(EnvironmentEndpoint.class) @EnableConfigurationProperties(EnvironmentEndpointProperties.class) public class EnvironmentEndpointAutoConfiguration { @@ -53,7 +53,7 @@ public EnvironmentEndpoint environmentEndpoint(Environment environment, Environm @Bean @ConditionalOnMissingBean @ConditionalOnBean(EnvironmentEndpoint.class) - @ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) + @ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB) public EnvironmentEndpointWebExtension environmentEndpointWebExtension(EnvironmentEndpoint environmentEndpoint, EnvironmentEndpointProperties properties) { return new EnvironmentEndpointWebExtension(environmentEndpoint, properties.getShowValues(), diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java index 346dd95a97d1..9e11a6907d81 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.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,7 +37,7 @@ */ @AutoConfiguration(after = FlywayAutoConfiguration.class) @ConditionalOnClass(Flyway.class) -@ConditionalOnAvailableEndpoint(endpoint = FlywayEndpoint.class) +@ConditionalOnAvailableEndpoint(FlywayEndpoint.class) public class FlywayEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java index ff47f03252eb..b7c1ff72a3a4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroups.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,9 +21,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Stream; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; @@ -32,8 +34,12 @@ import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group; import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Show; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath; +import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.boot.actuate.health.HttpCodeStatusMapper; @@ -51,7 +57,7 @@ * @author Phillip Webb * @author Madhura Bhave */ -class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups { +class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups, AdditionalPathsMapper { private static final Predicate ALL = (name) -> true; @@ -159,4 +165,20 @@ public HealthEndpointGroup get(String name) { return this.groups.get(name); } + @Override + public List getAdditionalPaths(EndpointId endpointId, WebServerNamespace webServerNamespace) { + if (!HealthEndpoint.ID.equals(endpointId)) { + return null; + } + return streamAllGroups().map(HealthEndpointGroup::getAdditionalPath) + .filter(Objects::nonNull) + .filter((additionalPath) -> additionalPath.hasNamespace(webServerNamespace)) + .map(AdditionalHealthEndpointPath::getValue) + .toList(); + } + + private Stream streamAllGroups() { + return Stream.concat(Stream.of(this.primaryGroup), this.groups.values().stream()); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java index a3a937dcfa02..8da63c9f5474 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.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,7 +33,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) +@ConditionalOnAvailableEndpoint(HealthEndpoint.class) @EnableConfigurationProperties(HealthEndpointProperties.class) @Import({ HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class }) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java index 8badfc736bd7..c4b78bb3b9c7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +73,8 @@ HttpCodeStatusMapper healthHttpCodeStatusMapper(HealthEndpointProperties propert } @Bean - @ConditionalOnMissingBean - HealthEndpointGroups healthEndpointGroups(ApplicationContext applicationContext, + @ConditionalOnMissingBean(HealthEndpointGroups.class) + AutoConfiguredHealthEndpointGroups healthEndpointGroups(ApplicationContext applicationContext, HealthEndpointProperties properties) { return new AutoConfiguredHealthEndpointGroups(applicationContext, properties); } 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 4a8d814ebd4e..7bed528a6504 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 @@ */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.REACTIVE) -@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, - exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) +@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB) class HealthEndpointReactiveWebExtensionConfiguration { @Bean @@ -60,7 +59,6 @@ ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension( } @Configuration(proxyBeanMethods = false) - @ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB) static class WebFluxAdditionalHealthEndpointPathsConfiguration { @Bean @@ -70,8 +68,7 @@ AdditionalHealthEndpointPathsWebFluxHandlerMapping healthEndpointWebFluxHandlerM ExposableWebEndpoint health = webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .orElseThrow( - () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); + .orElse(null); 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 a973b2f0fa4c..9a917398ccd1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +64,7 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnBean(HealthEndpoint.class) -@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, - exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) +@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB) class HealthEndpointWebExtensionConfiguration { @Bean @@ -81,12 +80,10 @@ private static ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEn return webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) .findFirst() - .orElseThrow( - () -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID))); + .orElse(null); } @ConditionalOnBean(DispatcherServlet.class) - @ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB) static class MvcAdditionalHealthEndpointPathsConfiguration { @Bean @@ -102,7 +99,6 @@ AdditionalHealthEndpointPathsWebMvcHandlerMapping healthEndpointWebMvcHandlerMap @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ResourceConfig.class) @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet") - @ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB) static class JerseyAdditionalHealthEndpointPathsConfiguration { @Bean @@ -163,7 +159,8 @@ private void register(ResourceConfig config) { JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory( WebServerNamespace.SERVER, this.groups); Collection endpointResources = resourceFactory - .createEndpointResources(mapping, Collections.singletonList(this.endpoint)) + .createEndpointResources(mapping, + (this.endpoint != null) ? Collections.singletonList(this.endpoint) : Collections.emptyList()) .stream() .filter(Objects::nonNull) .toList(); 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 db991e5676d2..540254b50e9f 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 @@ -78,7 +78,7 @@ public void setRoles(Set roles) { public static class Status { /** - * Comma-separated list of health statuses in order of severity. + * List of health statuses in order of severity. */ private List order = new ArrayList<>(); 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 2a9b13603f90..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfiguration.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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the - * new client and its own - * Spring Boot integration. - */ -@SuppressWarnings("removal") -@AutoConfiguration(after = InfluxDbAutoConfiguration.class) -@ConditionalOnClass(InfluxDB.class) -@ConditionalOnBean(InfluxDB.class) -@ConditionalOnEnabledHealthIndicator("influxdb") -@Deprecated(since = "3.2.0", forRemoval = true) -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..08b002a33093 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. @@ -16,12 +16,15 @@ package org.springframework.boot.actuate.autoconfigure.info; +import org.springframework.boot.actuate.autoconfigure.ssl.SslHealthIndicatorProperties; import org.springframework.boot.actuate.info.BuildInfoContributor; import org.springframework.boot.actuate.info.EnvironmentInfoContributor; import org.springframework.boot.actuate.info.GitInfoContributor; 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.actuate.info.SslInfoContributor; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -30,6 +33,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.info.BuildProperties; import org.springframework.boot.info.GitProperties; +import org.springframework.boot.info.SslInfo; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -45,7 +50,7 @@ * @since 2.0.0 */ @AutoConfiguration(after = ProjectInfoAutoConfiguration.class) -@EnableConfigurationProperties(InfoContributorProperties.class) +@EnableConfigurationProperties({ InfoContributorProperties.class, SslHealthIndicatorProperties.class }) public class InfoContributorAutoConfiguration { /** @@ -92,4 +97,25 @@ public OsInfoContributor osInfoContributor() { return new OsInfoContributor(); } + @Bean + @ConditionalOnEnabledInfoContributor(value = "process", fallback = InfoContributorFallback.DISABLE) + @Order(DEFAULT_ORDER) + public ProcessInfoContributor processInfoContributor() { + return new ProcessInfoContributor(); + } + + @Bean + @ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE) + @Order(DEFAULT_ORDER) + SslInfoContributor sslInfoContributor(SslInfo sslInfo) { + return new SslInfoContributor(sslInfo); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE) + SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) { + return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold()); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfiguration.java index 72f854dab409..28e99926783e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoEndpointAutoConfiguration.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,7 @@ * @since 2.0.0 */ @AutoConfiguration(after = InfoContributorAutoConfiguration.class) -@ConditionalOnAvailableEndpoint(endpoint = InfoEndpoint.class) +@ConditionalOnAvailableEndpoint(InfoEndpoint.class) public class InfoEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/integration/IntegrationGraphEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/integration/IntegrationGraphEndpointAutoConfiguration.java index 9a19b5cbb6d9..f6e10c24ebab 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/integration/IntegrationGraphEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/integration/IntegrationGraphEndpointAutoConfiguration.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,7 +39,7 @@ @AutoConfiguration(after = IntegrationAutoConfiguration.class) @ConditionalOnClass(IntegrationGraphServer.class) @ConditionalOnBean(IntegrationConfigurationBeanFactoryPostProcessor.class) -@ConditionalOnAvailableEndpoint(endpoint = IntegrationGraphEndpoint.class) +@ConditionalOnAvailableEndpoint(IntegrationGraphEndpoint.class) public class IntegrationGraphEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java index c2acc67446c3..f43a3196f478 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.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,7 +40,7 @@ */ @AutoConfiguration(after = LiquibaseAutoConfiguration.class) @ConditionalOnClass(SpringLiquibase.class) -@ConditionalOnAvailableEndpoint(endpoint = LiquibaseEndpoint.class) +@ConditionalOnAvailableEndpoint(LiquibaseEndpoint.class) public class LiquibaseEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.java new file mode 100644 index 000000000000..b32d8f9c4922 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/ConditionalOnEnabledLoggingExport.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.actuate.autoconfigure.logging; + +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 checks whether logging export is enabled. It + * matches if the value of the {@code management.logging.export.enabled} property is + * {@code true} or if it is not configured. If the {@link #value() logging exporter name} + * is set, the {@code management..logging.export.enabled} property can be used to + * control the behavior for the specific logging exporter. In that case, the + * exporter-specific property takes precedence over the global property. + * + * @author Moritz Halbritter + * @author Dmytro Nosan + * @since 3.4.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnEnabledLoggingExportCondition.class) +public @interface ConditionalOnEnabledLoggingExport { + + /** + * Name of the logging exporter. + * @return the name of the logging exporter + */ + String value() default ""; + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java index c3e88935d581..3ff32bf67ad2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LogFileWebEndpointAutoConfiguration.java @@ -42,7 +42,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = LogFileWebEndpoint.class) +@ConditionalOnAvailableEndpoint(LogFileWebEndpoint.class) @EnableConfigurationProperties(LogFileWebEndpointProperties.class) public class LogFileWebEndpointAutoConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java index a803aba2968e..58f734d1056b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/LoggersEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = LoggersEndpoint.class) +@ConditionalOnAvailableEndpoint(LoggersEndpoint.class) public class LoggersEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.java new file mode 100644 index 000000000000..eb328c816525 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportCondition.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.actuate.autoconfigure.logging; + +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 logging exporter is enabled. + * + * @author Moritz Halbritter + * @author Dmytro Nosan + * @see ConditionalOnEnabledLoggingExport + */ +class OnEnabledLoggingExportCondition extends SpringBootCondition { + + private static final String GLOBAL_PROPERTY = "management.logging.export.enabled"; + + private static final String EXPORTER_PROPERTY = "management.%s.logging.export.enabled"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + String loggingExporter = getExporterName(metadata); + if (StringUtils.hasLength(loggingExporter)) { + Boolean exporterLoggingEnabled = context.getEnvironment() + .getProperty(EXPORTER_PROPERTY.formatted(loggingExporter), Boolean.class); + if (exporterLoggingEnabled != null) { + return new ConditionOutcome(exporterLoggingEnabled, + ConditionMessage.forCondition(ConditionalOnEnabledLoggingExport.class) + .because(EXPORTER_PROPERTY.formatted(loggingExporter) + " is " + exporterLoggingEnabled)); + } + } + Boolean globalLoggingEnabled = context.getEnvironment().getProperty(GLOBAL_PROPERTY, Boolean.class); + if (globalLoggingEnabled != null) { + return new ConditionOutcome(globalLoggingEnabled, + ConditionMessage.forCondition(ConditionalOnEnabledLoggingExport.class) + .because(GLOBAL_PROPERTY + " is " + globalLoggingEnabled)); + } + return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnEnabledLoggingExport.class) + .because("is enabled by default")); + } + + private static String getExporterName(AnnotatedTypeMetadata metadata) { + Map attributes = metadata + .getAnnotationAttributes(ConditionalOnEnabledLoggingExport.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/logging/OpenTelemetryLoggingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfiguration.java new file mode 100644 index 000000000000..4d1e33ba8b9d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/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; + +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/SdkLoggerProviderBuilderCustomizer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/SdkLoggerProviderBuilderCustomizer.java new file mode 100644 index 000000000000..5961f1ec4d2a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/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; + +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/otlp/OtlpLoggingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfiguration.java new file mode 100644 index 000000000000..ec169dab024c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/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.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/otlp/OtlpLoggingConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java new file mode 100644 index 000000000000..14b491bd237d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.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.actuate.autoconfigure.logging.otlp; + +import java.util.Locale; + +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; +import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder; + +import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLoggingExport; +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; +import org.springframework.util.Assert; + +/** + * 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 otlpLoggingConnectionDetails(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 getUrl(Transport transport) { + Assert.state(transport == this.properties.getTransport(), + "Requested transport %s doesn't match configured transport %s".formatted(transport, + this.properties.getTransport())); + return this.properties.getEndpoint(); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean({ OtlpGrpcLogRecordExporter.class, OtlpHttpLogRecordExporter.class }) + @ConditionalOnBean(OtlpLoggingConnectionDetails.class) + @ConditionalOnEnabledLoggingExport("otlp") + static class Exporters { + + @Bean + @ConditionalOnProperty(prefix = "management.otlp.logging", name = "transport", havingValue = "http", + matchIfMissing = true) + OtlpHttpLogRecordExporter otlpHttpLogRecordExporter(OtlpLoggingProperties properties, + OtlpLoggingConnectionDetails connectionDetails) { + OtlpHttpLogRecordExporterBuilder builder = OtlpHttpLogRecordExporter.builder() + .setEndpoint(connectionDetails.getUrl(Transport.HTTP)) + .setTimeout(properties.getTimeout()) + .setConnectTimeout(properties.getConnectTimeout()) + .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); + properties.getHeaders().forEach(builder::addHeader); + return builder.build(); + } + + @Bean + @ConditionalOnProperty(prefix = "management.otlp.logging", name = "transport", havingValue = "grpc") + OtlpGrpcLogRecordExporter otlpGrpcLogRecordExporter(OtlpLoggingProperties properties, + OtlpLoggingConnectionDetails connectionDetails) { + OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder() + .setEndpoint(connectionDetails.getUrl(Transport.GRPC)) + .setTimeout(properties.getTimeout()) + .setConnectTimeout(properties.getConnectTimeout()) + .setCompression(properties.getCompression().name().toLowerCase(Locale.US)); + 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/otlp/OtlpLoggingConnectionDetails.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConnectionDetails.java new file mode 100644 index 000000000000..38cc3509e3a6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConnectionDetails.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.actuate.autoconfigure.logging.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. + * @param transport the transport to use + * @return the address to where logs will be published + */ + String getUrl(Transport transport); + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.java new file mode 100644 index 000000000000..4d06499f7ab7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingProperties.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.actuate.autoconfigure.logging.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); + + /** + * Connect timeout for the OTel collector connection. + */ + private Duration connectTimeout = Duration.ofSeconds(10); + + /** + * Transport used to send the spans. + */ + private Transport transport = Transport.HTTP; + + /** + * 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 Duration getConnectTimeout() { + return this.connectTimeout; + } + + public void setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public Transport getTransport() { + return this.transport; + } + + public void setTransport(Transport transport) { + this.transport = transport; + } + + 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/otlp/Transport.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/Transport.java new file mode 100644 index 000000000000..612025fda1e6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/Transport.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.logging.otlp; + +/** + * Transport used to send OTLP data. + * + * @author Moritz Halbritter + * @since 3.4.0 + */ +public enum Transport { + + /** + * HTTP transport. + */ + HTTP, + + /** + * gRPC transport. + */ + GRPC + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/package-info.java new file mode 100644 index 000000000000..4de6dfaf29ec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/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 exporting logs with OTLP. + */ +package org.springframework.boot.actuate.autoconfigure.logging.otlp; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/HeapDumpWebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/HeapDumpWebEndpointAutoConfiguration.java index dbb1c5ba51ab..659332eb855b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/HeapDumpWebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/HeapDumpWebEndpointAutoConfiguration.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. @@ -30,7 +30,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = HeapDumpWebEndpoint.class) +@ConditionalOnAvailableEndpoint(HeapDumpWebEndpoint.class) public class HeapDumpWebEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java index d5e00f03e587..63093737dafc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/management/ThreadDumpEndpointAutoConfiguration.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. @@ -30,7 +30,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = ThreadDumpEndpoint.class) +@ConditionalOnAvailableEndpoint(ThreadDumpEndpoint.class) public class ThreadDumpEndpointAutoConfiguration { @Bean 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/MetricsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsEndpointAutoConfiguration.java index c3fd9a6b0368..a1c0b8344b08 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsEndpointAutoConfiguration.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,7 +36,7 @@ */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class }) @ConditionalOnClass(Timed.class) -@ConditionalOnAvailableEndpoint(endpoint = MetricsEndpoint.class) +@ConditionalOnAvailableEndpoint(MetricsEndpoint.class) public class MetricsEndpointAutoConfiguration { @Bean 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 6ed7759cd14b..8a85337446f0 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-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -200,7 +200,7 @@ public Diskspace getDiskspace() { public static class Diskspace { /** - * Comma-separated list of paths to report disk metrics for. + * List of paths to report disk metrics for. */ private List paths = new ArrayList<>(Collections.singletonList(new File("."))); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticProperties.java index 11278d1bfa13..d26b143b0063 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticProperties.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. @@ -79,6 +79,12 @@ public class ElasticProperties extends StepRegistryProperties { */ private String apiKeyCredentials; + /** + * Whether to enable _source in the default index template when auto-creating the + * index. + */ + private boolean enableSource = false; + public String getHost() { return this.host; } @@ -159,4 +165,12 @@ public void setApiKeyCredentials(String apiKeyCredentials) { this.apiKeyCredentials = apiKeyCredentials; } + public boolean isEnableSource() { + return this.enableSource; + } + + public void setEnableSource(boolean enableSource) { + this.enableSource = enableSource; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapter.java index 082cf58f6d68..a44f61b8878c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapter.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. @@ -87,4 +87,9 @@ public String apiKeyCredentials() { return get(ElasticProperties::getApiKeyCredentials, ElasticConfig.super::apiKeyCredentials); } + @Override + public boolean enableSource() { + return get(ElasticProperties::isEnableSource, ElasticConfig.super::enableSource); + } + } 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 c7da21f488d1..82d7ea271796 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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,9 +30,12 @@ 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.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; +import org.springframework.core.task.VirtualThreadTaskExecutor; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to OTLP. @@ -47,12 +50,12 @@ @ConditionalOnBean(Clock.class) @ConditionalOnClass(OtlpMeterRegistry.class) @ConditionalOnEnabledMetricsExport("otlp") -@EnableConfigurationProperties({ OtlpProperties.class, OpenTelemetryProperties.class }) +@EnableConfigurationProperties({ OtlpMetricsProperties.class, OpenTelemetryProperties.class }) public class OtlpMetricsExportAutoConfiguration { - private final OtlpProperties properties; + private final OtlpMetricsProperties properties; - OtlpMetricsExportAutoConfiguration(OtlpProperties properties) { + OtlpMetricsExportAutoConfiguration(OtlpMetricsProperties properties) { this.properties = properties; } @@ -66,24 +69,33 @@ OtlpMetricsConnectionDetails otlpMetricsConnectionDetails() { @ConditionalOnMissingBean OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties, OtlpMetricsConnectionDetails connectionDetails, Environment environment) { - return new OtlpPropertiesConfigAdapter(this.properties, openTelemetryProperties, connectionDetails, + return new OtlpMetricsPropertiesConfigAdapter(this.properties, openTelemetryProperties, connectionDetails, environment); } @Bean @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.PLATFORM) public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) { return new OtlpMeterRegistry(otlpConfig, clock); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + public OtlpMeterRegistry otlpMeterRegistryVirtualThreads(OtlpConfig otlpConfig, Clock clock) { + VirtualThreadTaskExecutor taskExecutor = new VirtualThreadTaskExecutor("otlp-meter-registry-"); + return new OtlpMeterRegistry(otlpConfig, clock, taskExecutor.getVirtualThreadFactory()); + } + /** - * Adapts {@link OtlpProperties} to {@link OtlpMetricsConnectionDetails}. + * Adapts {@link OtlpMetricsProperties} to {@link OtlpMetricsConnectionDetails}. */ static class PropertiesOtlpMetricsConnectionDetails implements OtlpMetricsConnectionDetails { - private final OtlpProperties properties; + private final OtlpMetricsProperties properties; - PropertiesOtlpMetricsConnectionDetails(OtlpProperties properties) { + PropertiesOtlpMetricsConnectionDetails(OtlpMetricsProperties properties) { this.properties = properties; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsProperties.java new file mode 100644 index 000000000000..17e5fe771a27 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsProperties.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.autoconfigure.metrics.export.otlp; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.micrometer.registry.otlp.AggregationTemporality; +import io.micrometer.registry.otlp.HistogramFlavor; + +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 + * export. + * + * @author Eddú Meléndez + * @author Jonatan Ivanov + * @since 3.0.0 + */ +@ConfigurationProperties(prefix = "management.otlp.metrics.export") +public class OtlpMetricsProperties extends StepRegistryProperties { + + /** + * URI of the OTLP server. + */ + private String url = "http://localhost:4318/v1/metrics"; + + /** + * Aggregation temporality of sums. It defines the way additive values are expressed. + * This setting depends on the backend you use, some only support one temporality. + */ + private AggregationTemporality aggregationTemporality = AggregationTemporality.CUMULATIVE; + + /** + * Monitored resource's attributes. + */ + private Map resourceAttributes; + + /** + * Headers for the exported metrics. + */ + private Map headers; + + /** + * Histogram type to be preferred when histogram publishing is enabled. + */ + private HistogramFlavor histogramFlavor = HistogramFlavor.EXPLICIT_BUCKET_HISTOGRAM; + + /** + * Max scale to use for exponential histograms, if configured. + */ + private int maxScale = 20; + + /** + * Maximum number of buckets to be used for exponential histograms, if configured. + * This has no effect on explicit bucket histograms. + */ + private int maxBucketCount = 160; + + /** + * Time unit for exported metrics. + */ + private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + public AggregationTemporality getAggregationTemporality() { + return this.aggregationTemporality; + } + + public void setAggregationTemporality(AggregationTemporality aggregationTemporality) { + 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; + } + + public Map getHeaders() { + return this.headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public HistogramFlavor getHistogramFlavor() { + return this.histogramFlavor; + } + + public void setHistogramFlavor(HistogramFlavor histogramFlavor) { + this.histogramFlavor = histogramFlavor; + } + + public int getMaxScale() { + return this.maxScale; + } + + public void setMaxScale(int maxScale) { + this.maxScale = maxScale; + } + + public int getMaxBucketCount() { + return this.maxBucketCount; + } + + public void setMaxBucketCount(int maxBucketCount) { + this.maxBucketCount = maxBucketCount; + } + + 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/OtlpMetricsPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java new file mode 100644 index 000000000000..b6268dee51d2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.HistogramFlavor; +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 OtlpMetricsProperties} to an {@link OtlpConfig}. + * + * @author Eddú Meléndez + * @author Jonatan Ivanov + * @author Moritz Halbritter + */ +class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter + implements OtlpConfig { + + /** + * 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; + + OtlpMetricsPropertiesConfigAdapter(OtlpMetricsProperties properties, + OpenTelemetryProperties openTelemetryProperties, OtlpMetricsConnectionDetails connectionDetails, + Environment environment) { + super(properties); + this.connectionDetails = connectionDetails; + this.openTelemetryProperties = openTelemetryProperties; + this.environment = environment; + } + + @Override + public String prefix() { + return "management.otlp.metrics.export"; + } + + @Override + public String url() { + return get((properties) -> this.connectionDetails.getUrl(), OtlpConfig.super::url); + } + + @Override + public AggregationTemporality aggregationTemporality() { + return get(OtlpMetricsProperties::getAggregationTemporality, OtlpConfig.super::aggregationTemporality); + } + + @Override + @SuppressWarnings("removal") + public Map resourceAttributes() { + Map resourceAttributes = this.openTelemetryProperties.getResourceAttributes(); + Map result = new HashMap<>((!CollectionUtils.isEmpty(resourceAttributes)) ? resourceAttributes + : get(OtlpMetricsProperties::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 + public Map headers() { + return get(OtlpMetricsProperties::getHeaders, OtlpConfig.super::headers); + } + + @Override + public HistogramFlavor histogramFlavor() { + return get(OtlpMetricsProperties::getHistogramFlavor, OtlpConfig.super::histogramFlavor); + } + + @Override + public int maxScale() { + return get(OtlpMetricsProperties::getMaxScale, OtlpConfig.super::maxScale); + } + + @Override + public int maxBucketCount() { + return get(OtlpMetricsProperties::getMaxBucketCount, OtlpConfig.super::maxBucketCount); + } + + @Override + public TimeUnit baseTimeUnit() { + return get(OtlpMetricsProperties::getBaseTimeUnit, OtlpConfig.super::baseTimeUnit); + } + +} 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 deleted file mode 100644 index e9a038d3e664..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java +++ /dev/null @@ -1,108 +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.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 - * export. - * - * @author Eddú Meléndez - * @author Jonatan Ivanov - * @since 3.0.0 - */ -@ConfigurationProperties(prefix = "management.otlp.metrics.export") -public class OtlpProperties extends StepRegistryProperties { - - /** - * URI of the OLTP server. - */ - private String url = "http://localhost:4318/v1/metrics"; - - /** - * Aggregation temporality of sums. It defines the way additive values are expressed. - * This setting depends on the backend you use, some only support one temporality. - */ - private AggregationTemporality aggregationTemporality = AggregationTemporality.CUMULATIVE; - - /** - * Monitored resource's attributes. - */ - private Map resourceAttributes; - - /** - * Headers for the exported metrics. - */ - private Map headers; - - /** - * Time unit for exported metrics. - */ - private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public AggregationTemporality getAggregationTemporality() { - return this.aggregationTemporality; - } - - public void setAggregationTemporality(AggregationTemporality aggregationTemporality) { - 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; - } - - public Map getHeaders() { - return this.headers; - } - - 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 deleted file mode 100644 index f329d7bf17ae..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java +++ /dev/null @@ -1,99 +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.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; - -/** - * 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 { - - /** - * Default value for application name if {@code spring.application.name} is not set. - */ - private static final String DEFAULT_APPLICATION_NAME = "application"; - - 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 - public String prefix() { - return "management.otlp.metrics.export"; - } - - @Override - public String url() { - return get((properties) -> this.connectionDetails.getUrl(), OtlpConfig.super::url); - } - - @Override - public AggregationTemporality aggregationTemporality() { - return get(OtlpProperties::getAggregationTemporality, OtlpConfig.super::aggregationTemporality); - } - - @Override - @SuppressWarnings("removal") - public Map 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()); - return Collections.unmodifiableMap(result); - } - - private String getApplicationName() { - return this.environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); - } - - @Override - 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..3b1f705b8aa0 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,15 @@ 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.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 +57,34 @@ 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); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnAvailableEndpoint(endpoint = PrometheusScrapeEndpoint.class) - public static class PrometheusScrapeEndpointConfiguration { - - @Bean - @ConditionalOnMissingBean - public PrometheusScrapeEndpoint prometheusEndpoint(CollectorRegistry collectorRegistry) { - return new PrometheusScrapeEndpoint(collectorRegistry); - } - + PrometheusRegistry prometheusRegistry() { + return new PrometheusRegistry(); } - /** - * 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"; + @ConditionalOnAvailableEndpoint(PrometheusScrapeEndpoint.class) + 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, + org.springframework.boot.actuate.metrics.export.prometheus.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..c69429a48a96 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 @@ -55,9 +54,16 @@ public class PrometheusProperties { /** * Histogram type for backing DistributionSummary and Timer. + * @deprecated since 3.3.0 for removal in 3.5.0 */ + @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 +77,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 +108,10 @@ public Pushgateway getPushgateway() { return this.pushgateway; } + public Map getProperties() { + return this.properties; + } + /** * Configuration options for push-based interaction with Prometheus. */ @@ -210,4 +223,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..25c94dd52912 --- /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(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/jersey/JerseyServerMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java index 27d9729cc80f..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.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,13 +43,14 @@ * @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) +@ConditionalOnClass({ ResourceConfig.class, ObservationApplicationEventListener.class }) +@ConditionalOnBean({ ResourceConfig.class, ObservationRegistry.class }) +@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class }) public class JerseyServerMetricsAutoConfiguration { private final ObservationProperties observationProperties; @@ -66,17 +60,11 @@ public JerseyServerMetricsAutoConfiguration(ObservationProperties observationPro } @Bean - @ConditionalOnMissingBean(JerseyTagsProvider.class) - public DefaultJerseyTagsProvider jerseyTagsProvider() { - return new DefaultJerseyTagsProvider(); - } - - @Bean - public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(MeterRegistry meterRegistry, - JerseyTagsProvider tagsProvider) { + ResourceConfigCustomizer jerseyServerObservationResourceConfigCustomizer(ObservationRegistry observationRegistry, + ObjectProvider jerseyObservationConvention) { String metricName = this.observationProperties.getHttp().getServer().getRequests().getName(); - return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry, tagsProvider, metricName, - true, new AnnotationUtilsAnnotationFinder())); + return (config) -> config.register(new ObservationApplicationEventListener(observationRegistry, metricName, + jerseyObservationConvention.getIfAvailable())); } @Bean @@ -89,16 +77,4 @@ public MeterFilter jerseyMetricsUriTagFilter(MetricsProperties metricsProperties metricsProperties.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); - } - - } - } 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 b9230b45dd12..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; @@ -131,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); } } @@ -143,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); } 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 08de1a01c104..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 @@ public class ObservationProperties { */ private Map enable = new LinkedHashMap<>(); + private final LongTaskTimer longTaskTimer = new LongTaskTimer(); + public Map getEnable() { return this.enable; } @@ -65,6 +67,10 @@ public void setKeyValues(Map keyValues) { this.keyValues = keyValues; } + public LongTaskTimer getLongTaskTimer() { + return this.longTaskTimer; + } + public static class Http { private final Client client = new Client(); @@ -135,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/opentelemetry/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java index f87bfa6548dd..720e79142f61 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.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. @@ -51,10 +52,12 @@ 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 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, @@ -72,9 +75,13 @@ OpenTelemetrySdk openTelemetry(ObjectProvider tracerProvider, @ConditionalOnMissingBean Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) { String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); - return Resource.getDefault() - .merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_NAME, applicationName))) - .merge(toResource(properties)); + 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) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java index a97d2bcdd4a0..e12d92a8fe5d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.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. @@ -42,7 +42,7 @@ */ @AutoConfiguration(after = QuartzAutoConfiguration.class) @ConditionalOnClass(Scheduler.class) -@ConditionalOnAvailableEndpoint(endpoint = QuartzEndpoint.class) +@ConditionalOnAvailableEndpoint(QuartzEndpoint.class) @EnableConfigurationProperties(QuartzEndpointProperties.class) public class QuartzEndpointAutoConfiguration { @@ -56,7 +56,7 @@ public QuartzEndpoint quartzEndpoint(Scheduler scheduler, ObjectProvider queryObservationConvention, ObjectProvider queryParametersTagProvider) { - return (connectionFactory) -> { + 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); - return ProxyConnectionFactory.builder(connectionFactory).listener(listener).build(); + builder.listener(listener); }; } 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..410de254133d --- /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(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/ScheduledTasksEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksEndpointAutoConfiguration.java index 45d11685231a..d983c5f68c65 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/scheduling/ScheduledTasksEndpointAutoConfiguration.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,7 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = ScheduledTasksEndpoint.class) +@ConditionalOnAvailableEndpoint(ScheduledTasksEndpoint.class) public class ScheduledTasksEndpointAutoConfiguration { @Bean 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..16e91c6e9063 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. @@ -35,6 +35,7 @@ import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.MergedAnnotation; @@ -43,7 +44,9 @@ import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; @@ -52,6 +55,7 @@ * endpoint locations. * * @author Madhura Bhave + * @author Phillip Webb * @since 2.0.0 */ public final class EndpointRequest { @@ -115,32 +119,129 @@ public static LinksServerWebExchangeMatcher toLinks() { return new LinksServerWebExchangeMatcher(); } + /** + * Returns a matcher that includes additional paths under a {@link WebServerNamespace} + * for the specified {@link Endpoint actuator endpoints}. For example: + *

+	 * EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, "health")
+	 * 
+ * @param webServerNamespace the web server namespace + * @param endpoints the endpoints to include + * @return the configured {@link RequestMatcher} + * @since 3.4.0 + */ + public static AdditionalPathsEndpointServerWebExchangeMatcher toAdditionalPaths( + WebServerNamespace webServerNamespace, Class... endpoints) { + return new AdditionalPathsEndpointServerWebExchangeMatcher(webServerNamespace, endpoints); + } + + /** + * Returns a matcher that includes additional paths under a {@link WebServerNamespace} + * for the specified {@link Endpoint actuator endpoints}. For example: + *
+	 * EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, HealthEndpoint.class)
+	 * 
+ * @param webServerNamespace the web server namespace + * @param endpoints the endpoints to include + * @return the configured {@link RequestMatcher} + * @since 3.4.0 + */ + public static AdditionalPathsEndpointServerWebExchangeMatcher toAdditionalPaths( + WebServerNamespace webServerNamespace, String... endpoints) { + return new AdditionalPathsEndpointServerWebExchangeMatcher(webServerNamespace, endpoints); + } + /** * Base class for supported request matchers. */ - private abstract static class AbstractWebExchangeMatcher extends ApplicationContextServerWebExchangeMatcher { + private abstract static class AbstractWebExchangeMatcher extends ApplicationContextServerWebExchangeMatcher { - private ManagementPortType managementPortType; + private volatile ServerWebExchangeMatcher delegate; - AbstractWebExchangeMatcher(Class contextClass) { + private volatile ManagementPortType managementPortType; + + AbstractWebExchangeMatcher(Class contextClass) { super(contextClass); } + @Override + protected void initialized(Supplier supplier) { + this.delegate = createDelegate(supplier); + } + + private ServerWebExchangeMatcher createDelegate(Supplier context) { + try { + return createDelegate(context.get()); + } + catch (NoSuchBeanDefinitionException ex) { + return EMPTY_MATCHER; + } + } + + protected abstract ServerWebExchangeMatcher createDelegate(C context); + + protected final List getDelegateMatchers(Set paths) { + return paths.stream().map(this::getDelegateMatcher).collect(Collectors.toCollection(ArrayList::new)); + } + + private PathPatternParserServerWebExchangeMatcher getDelegateMatcher(String path) { + return new PathPatternParserServerWebExchangeMatcher(path + "/**"); + } + + @Override + protected Mono matches(ServerWebExchange exchange, Supplier context) { + return this.delegate.matches(exchange); + } + @Override protected boolean ignoreApplicationContext(ApplicationContext applicationContext) { - if (this.managementPortType == null) { - this.managementPortType = ManagementPortType.get(applicationContext.getEnvironment()); + ManagementPortType managementPortType = this.managementPortType; + if (managementPortType == null) { + managementPortType = ManagementPortType.get(applicationContext.getEnvironment()); + this.managementPortType = managementPortType; + } + return ignoreApplicationContext(applicationContext, managementPortType); + } + + protected boolean ignoreApplicationContext(ApplicationContext applicationContext, + ManagementPortType managementPortType) { + return managementPortType == ManagementPortType.DIFFERENT + && !hasWebServerNamespace(applicationContext, WebServerNamespace.MANAGEMENT); + } + + protected final boolean hasWebServerNamespace(ApplicationContext applicationContext, + WebServerNamespace webServerNamespace) { + if (applicationContext.getParent() == null) { + return WebServerNamespace.SERVER.equals(webServerNamespace); + } + String parentContextId = applicationContext.getParent().getId(); + return applicationContext.getId().equals(parentContextId + ":" + webServerNamespace); + } + + protected final String toString(List endpoints, String emptyValue) { + return (!endpoints.isEmpty()) ? endpoints.stream() + .map(this::getEndpointId) + .map(Object::toString) + .collect(Collectors.joining(", ", "[", "]")) : emptyValue; + } + + protected final EndpointId getEndpointId(Object source) { + if (source instanceof EndpointId endpointId) { + return endpointId; + } + if (source instanceof String string) { + return EndpointId.of(string); } - if (this.managementPortType == ManagementPortType.DIFFERENT) { - if (applicationContext.getParent() == null) { - return true; - } - String managementContextId = applicationContext.getParent().getId() + ":management"; - if (!managementContextId.equals(applicationContext.getId())) { - return true; - } + if (source instanceof Class) { + return getEndpointId((Class) source); } - return false; + throw new IllegalStateException("Unsupported source " + source); + } + + private EndpointId getEndpointId(Class source) { + MergedAnnotation annotation = MergedAnnotations.from(source).get(Endpoint.class); + Assert.state(annotation.isPresent(), () -> "Class " + source + " is not annotated with @Endpoint"); + return EndpointId.of(annotation.getString("id")); } } @@ -157,8 +258,6 @@ public static final class EndpointServerWebExchangeMatcher extends AbstractWebEx private final boolean includeLinks; - private volatile ServerWebExchangeMatcher delegate; - private EndpointServerWebExchangeMatcher(boolean includeLinks) { this(Collections.emptyList(), Collections.emptyList(), includeLinks); } @@ -195,48 +294,22 @@ public EndpointServerWebExchangeMatcher excludingLinks() { } @Override - protected void initialized(Supplier pathMappedEndpoints) { - this.delegate = createDelegate(pathMappedEndpoints); - } - - private ServerWebExchangeMatcher createDelegate(Supplier pathMappedEndpoints) { - try { - return createDelegate(pathMappedEndpoints.get()); - } - catch (NoSuchBeanDefinitionException ex) { - return EMPTY_MATCHER; - } - } - - private ServerWebExchangeMatcher createDelegate(PathMappedEndpoints pathMappedEndpoints) { + protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints) { Set paths = new LinkedHashSet<>(); if (this.includes.isEmpty()) { - paths.addAll(pathMappedEndpoints.getAllPaths()); + paths.addAll(endpoints.getAllPaths()); } - streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add); - streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove); + streamPaths(this.includes, endpoints).forEach(paths::add); + streamPaths(this.excludes, endpoints).forEach(paths::remove); List delegateMatchers = getDelegateMatchers(paths); - if (this.includeLinks && StringUtils.hasText(pathMappedEndpoints.getBasePath())) { + if (this.includeLinks && StringUtils.hasText(endpoints.getBasePath())) { delegateMatchers.add(new LinksServerWebExchangeMatcher()); } return new OrServerWebExchangeMatcher(delegateMatchers); } - private Stream streamPaths(List source, PathMappedEndpoints pathMappedEndpoints) { - return source.stream().filter(Objects::nonNull).map(this::getEndpointId).map(pathMappedEndpoints::getPath); - } - - @Override - protected Mono matches(ServerWebExchange exchange, Supplier context) { - return this.delegate.matches(exchange); - } - - private List getDelegateMatchers(Set paths) { - return paths.stream().map(this::getDelegateMatcher).collect(Collectors.toCollection(ArrayList::new)); - } - - private PathPatternParserServerWebExchangeMatcher getDelegateMatcher(String path) { - return new PathPatternParserServerWebExchangeMatcher(path + "/**"); + private Stream streamPaths(List source, PathMappedEndpoints endpoints) { + return source.stream().filter(Objects::nonNull).map(this::getEndpointId).map(endpoints::getPath); } @Override @@ -245,32 +318,6 @@ public String toString() { toString(this.includes, "[*]"), toString(this.excludes, "[]"), this.includeLinks); } - private String toString(List endpoints, String emptyValue) { - return (!endpoints.isEmpty()) ? endpoints.stream() - .map(this::getEndpointId) - .map(Object::toString) - .collect(Collectors.joining(", ", "[", "]")) : emptyValue; - } - - private EndpointId getEndpointId(Object source) { - if (source instanceof EndpointId endpointId) { - return endpointId; - } - if (source instanceof String string) { - return EndpointId.of(string); - } - if (source instanceof Class) { - return getEndpointId((Class) source); - } - throw new IllegalStateException("Unsupported source " + source); - } - - private EndpointId getEndpointId(Class source) { - MergedAnnotation annotation = MergedAnnotations.from(source).get(Endpoint.class); - Assert.state(annotation.isPresent(), () -> "Class " + source + " is not annotated with @Endpoint"); - return EndpointId.of(annotation.getString("id")); - } - } /** @@ -278,18 +325,12 @@ private EndpointId getEndpointId(Class source) { */ public static final class LinksServerWebExchangeMatcher extends AbstractWebExchangeMatcher { - private volatile ServerWebExchangeMatcher delegate; - private LinksServerWebExchangeMatcher() { super(WebEndpointProperties.class); } @Override - protected void initialized(Supplier properties) { - this.delegate = createDelegate(properties.get()); - } - - private ServerWebExchangeMatcher createDelegate(WebEndpointProperties properties) { + protected ServerWebExchangeMatcher createDelegate(WebEndpointProperties properties) { if (StringUtils.hasText(properties.getBasePath())) { return new OrServerWebExchangeMatcher( new PathPatternParserServerWebExchangeMatcher(properties.getBasePath()), @@ -299,8 +340,67 @@ private ServerWebExchangeMatcher createDelegate(WebEndpointProperties properties } @Override - protected Mono matches(ServerWebExchange exchange, Supplier context) { - return this.delegate.matches(exchange); + public String toString() { + return String.format("LinksServerWebExchangeMatcher"); + } + + } + + /** + * The {@link ServerWebExchangeMatcher} used to match against additional paths for + * {@link Endpoint actuator endpoints}. + */ + public static class AdditionalPathsEndpointServerWebExchangeMatcher + extends AbstractWebExchangeMatcher { + + private final WebServerNamespace webServerNamespace; + + private final List endpoints; + + AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, String... endpoints) { + this(webServerNamespace, Arrays.asList((Object[]) endpoints)); + } + + AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, Class... endpoints) { + this(webServerNamespace, Arrays.asList((Object[]) endpoints)); + } + + private AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, + List endpoints) { + super(PathMappedEndpoints.class); + Assert.notNull(webServerNamespace, "'webServerNamespace' must not be null"); + Assert.notNull(endpoints, "'endpoints' must not be null"); + Assert.notEmpty(endpoints, "'endpoints' must not be empty"); + this.webServerNamespace = webServerNamespace; + this.endpoints = endpoints; + } + + @Override + protected boolean ignoreApplicationContext(ApplicationContext applicationContext, + ManagementPortType managementPortType) { + return !hasWebServerNamespace(applicationContext, this.webServerNamespace); + } + + @Override + protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints) { + Set paths = this.endpoints.stream() + .filter(Objects::nonNull) + .map(this::getEndpointId) + .flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + List delegateMatchers = getDelegateMatchers(paths); + return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrServerWebExchangeMatcher(delegateMatchers) + : EMPTY_MATCHER; + } + + private Stream streamAdditionalPaths(PathMappedEndpoints pathMappedEndpoints, EndpointId endpointId) { + return pathMappedEndpoints.getAdditionalPaths(this.webServerNamespace, endpointId).stream(); + } + + @Override + public String toString() { + return String.format("AdditionalPathsEndpointServerWebExchangeMatcher endpoints=%s, webServerNamespace=%s", + toString(this.endpoints, ""), this.webServerNamespace); } } 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 e9da837d148e..3567604974b0 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 @@ -21,6 +21,7 @@ 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; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -40,6 +41,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.web.cors.reactive.PreFlightRequestHandler; import org.springframework.web.cors.reactive.PreFlightRequestWebFilter; @@ -66,7 +68,7 @@ public class ReactiveManagementWebSecurityAutoConfiguration { @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, PreFlightRequestHandler handler) { http.authorizeExchange((exchanges) -> { - exchanges.matchers(EndpointRequest.to(HealthEndpoint.class)).permitAll(); + exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); exchanges.anyExchange().authenticated(); }); PreFlightRequestWebFilter filter = new PreFlightRequestWebFilter(handler); @@ -76,6 +78,14 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, return http.build(); } + private ServerWebExchangeMatcher healthMatcher() { + return EndpointRequest.to(HealthEndpoint.class); + } + + private ServerWebExchangeMatcher additionalHealthPathsMatcher() { + return EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, HealthEndpoint.class); + } + @Bean @ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class }) ReactiveAuthenticationManager denyAllAuthenticationManager() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java index b8a63d0c4cc0..3a85bc894529 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java @@ -35,15 +35,18 @@ import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider; import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher; import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; @@ -116,6 +119,38 @@ public static LinksRequestMatcher toLinks() { return new LinksRequestMatcher(); } + /** + * Returns a matcher that includes additional paths under a {@link WebServerNamespace} + * for the specified {@link Endpoint actuator endpoints}. For example: + *
+	 * EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, "health")
+	 * 
+ * @param webServerNamespace the web server namespace + * @param endpoints the endpoints to include + * @return the configured {@link RequestMatcher} + * @since 3.4.0 + */ + public static AdditionalPathsEndpointRequestMatcher toAdditionalPaths(WebServerNamespace webServerNamespace, + Class... endpoints) { + return new AdditionalPathsEndpointRequestMatcher(webServerNamespace, endpoints); + } + + /** + * Returns a matcher that includes additional paths under a {@link WebServerNamespace} + * for the specified {@link Endpoint actuator endpoints}. For example: + *
+	 * EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, HealthEndpoint.class)
+	 * 
+ * @param webServerNamespace the web server namespace + * @param endpoints the endpoints to include + * @return the configured {@link RequestMatcher} + * @since 3.4.0 + */ + public static AdditionalPathsEndpointRequestMatcher toAdditionalPaths(WebServerNamespace webServerNamespace, + String... endpoints) { + return new AdditionalPathsEndpointRequestMatcher(webServerNamespace, endpoints); + } + /** * Base class for supported request matchers. */ @@ -124,7 +159,7 @@ private abstract static class AbstractRequestMatcher private volatile RequestMatcher delegate; - private ManagementPortType managementPortType; + private volatile ManagementPortType managementPortType; AbstractRequestMatcher() { super(WebApplicationContext.class); @@ -132,11 +167,25 @@ private abstract static class AbstractRequestMatcher @Override protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) { - if (this.managementPortType == null) { - this.managementPortType = ManagementPortType.get(applicationContext.getEnvironment()); + ManagementPortType managementPortType = this.managementPortType; + if (managementPortType == null) { + managementPortType = ManagementPortType.get(applicationContext.getEnvironment()); + this.managementPortType = managementPortType; } - return this.managementPortType == ManagementPortType.DIFFERENT - && !WebServerApplicationContext.hasServerNamespace(applicationContext, "management"); + return ignoreApplicationContext(applicationContext, managementPortType); + } + + protected boolean ignoreApplicationContext(WebApplicationContext applicationContext, + ManagementPortType managementPortType) { + return managementPortType == ManagementPortType.DIFFERENT + && !hasWebServerNamespace(applicationContext, WebServerNamespace.MANAGEMENT); + } + + protected final boolean hasWebServerNamespace(ApplicationContext applicationContext, + WebServerNamespace webServerNamespace) { + return WebServerApplicationContext.hasServerNamespace(applicationContext, webServerNamespace.getValue()) + || (webServerNamespace.equals(WebServerNamespace.SERVER) + && !(applicationContext instanceof WebServerApplicationContext)); } @Override @@ -161,6 +210,13 @@ private RequestMatcher createDelegate(WebApplicationContext context) { protected abstract RequestMatcher createDelegate(WebApplicationContext context, RequestMatcherFactory requestMatcherFactory); + protected final List getDelegateMatchers(RequestMatcherFactory requestMatcherFactory, + RequestMatcherProvider matcherProvider, Set paths) { + return paths.stream() + .map((path) -> requestMatcherFactory.antPath(matcherProvider, path, "/**")) + .collect(Collectors.toCollection(ArrayList::new)); + } + protected List getLinksMatchers(RequestMatcherFactory requestMatcherFactory, RequestMatcherProvider matcherProvider, String basePath) { List linksMatchers = new ArrayList<>(); @@ -178,6 +234,32 @@ protected RequestMatcherProvider getRequestMatcherProvider(WebApplicationContext } } + protected final String toString(List endpoints, String emptyValue) { + return (!endpoints.isEmpty()) ? endpoints.stream() + .map(this::getEndpointId) + .map(Object::toString) + .collect(Collectors.joining(", ", "[", "]")) : emptyValue; + } + + protected final EndpointId getEndpointId(Object source) { + if (source instanceof EndpointId endpointId) { + return endpointId; + } + if (source instanceof String string) { + return EndpointId.of(string); + } + if (source instanceof Class sourceClass) { + return getEndpointId(sourceClass); + } + throw new IllegalStateException("Unsupported source " + source); + } + + private EndpointId getEndpointId(Class source) { + MergedAnnotation annotation = MergedAnnotations.from(source).get(Endpoint.class); + Assert.state(annotation.isPresent(), () -> "Class " + source + " is not annotated with @Endpoint"); + return EndpointId.of(annotation.getString("id")); + } + } /** @@ -228,31 +310,24 @@ public EndpointRequestMatcher excludingLinks() { @Override protected RequestMatcher createDelegate(WebApplicationContext context, RequestMatcherFactory requestMatcherFactory) { - PathMappedEndpoints pathMappedEndpoints = context.getBean(PathMappedEndpoints.class); + PathMappedEndpoints endpoints = context.getBean(PathMappedEndpoints.class); RequestMatcherProvider matcherProvider = getRequestMatcherProvider(context); Set paths = new LinkedHashSet<>(); if (this.includes.isEmpty()) { - paths.addAll(pathMappedEndpoints.getAllPaths()); + paths.addAll(endpoints.getAllPaths()); } - streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add); - streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove); + streamPaths(this.includes, endpoints).forEach(paths::add); + streamPaths(this.excludes, endpoints).forEach(paths::remove); List delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths); - String basePath = pathMappedEndpoints.getBasePath(); + String basePath = endpoints.getBasePath(); if (this.includeLinks && StringUtils.hasText(basePath)) { delegateMatchers.addAll(getLinksMatchers(requestMatcherFactory, matcherProvider, basePath)); } return new OrRequestMatcher(delegateMatchers); } - private Stream streamPaths(List source, PathMappedEndpoints pathMappedEndpoints) { - return source.stream().filter(Objects::nonNull).map(this::getEndpointId).map(pathMappedEndpoints::getPath); - } - - private List getDelegateMatchers(RequestMatcherFactory requestMatcherFactory, - RequestMatcherProvider matcherProvider, Set paths) { - return paths.stream() - .map((path) -> requestMatcherFactory.antPath(matcherProvider, path, "/**")) - .collect(Collectors.toCollection(ArrayList::new)); + private Stream streamPaths(List source, PathMappedEndpoints endpoints) { + return source.stream().filter(Objects::nonNull).map(this::getEndpointId).map(endpoints::getPath); } @Override @@ -261,32 +336,6 @@ public String toString() { toString(this.includes, "[*]"), toString(this.excludes, "[]"), this.includeLinks); } - private String toString(List endpoints, String emptyValue) { - return (!endpoints.isEmpty()) ? endpoints.stream() - .map(this::getEndpointId) - .map(Object::toString) - .collect(Collectors.joining(", ", "[", "]")) : emptyValue; - } - - private EndpointId getEndpointId(Object source) { - if (source instanceof EndpointId endpointId) { - return endpointId; - } - if (source instanceof String string) { - return EndpointId.of(string); - } - if (source instanceof Class) { - return getEndpointId((Class) source); - } - throw new IllegalStateException("Unsupported source " + source); - } - - private EndpointId getEndpointId(Class source) { - MergedAnnotation annotation = MergedAnnotations.from(source).get(Endpoint.class); - Assert.state(annotation.isPresent(), () -> "Class " + source + " is not annotated with @Endpoint"); - return EndpointId.of(annotation.getString("id")); - } - } /** @@ -306,6 +355,70 @@ protected RequestMatcher createDelegate(WebApplicationContext context, return EMPTY_MATCHER; } + @Override + public String toString() { + return String.format("LinksRequestMatcher"); + } + + } + + /** + * The request matcher used to match against additional paths for {@link Endpoint + * actuator endpoints}. + */ + public static class AdditionalPathsEndpointRequestMatcher extends AbstractRequestMatcher { + + private final WebServerNamespace webServerNamespace; + + private final List endpoints; + + AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, String... endpoints) { + this(webServerNamespace, Arrays.asList((Object[]) endpoints)); + } + + AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, Class... endpoints) { + this(webServerNamespace, Arrays.asList((Object[]) endpoints)); + } + + private AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, List endpoints) { + Assert.notNull(webServerNamespace, "'webServerNamespace' must not be null"); + Assert.notNull(endpoints, "'endpoints' must not be null"); + Assert.notEmpty(endpoints, "'endpoints' must not be empty"); + this.webServerNamespace = webServerNamespace; + this.endpoints = endpoints; + } + + @Override + protected boolean ignoreApplicationContext(WebApplicationContext applicationContext, + ManagementPortType managementPortType) { + return !hasWebServerNamespace(applicationContext, this.webServerNamespace); + } + + @Override + protected RequestMatcher createDelegate(WebApplicationContext context, + RequestMatcherFactory requestMatcherFactory) { + PathMappedEndpoints endpoints = context.getBean(PathMappedEndpoints.class); + RequestMatcherProvider matcherProvider = getRequestMatcherProvider(context); + Set paths = this.endpoints.stream() + .filter(Objects::nonNull) + .map(this::getEndpointId) + .flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + List delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths); + return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrRequestMatcher(delegateMatchers) + : EMPTY_MATCHER; + } + + private Stream streamAdditionalPaths(PathMappedEndpoints pathMappedEndpoints, EndpointId endpointId) { + return pathMappedEndpoints.getAdditionalPaths(this.webServerNamespace, endpointId).stream(); + } + + @Override + public String toString() { + return String.format("AdditionalPathsEndpointRequestMatcher endpoints=%s, webServerNamespace=%s", + toString(this.endpoints, ""), this.webServerNamespace); + } + } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java index d6bc5b11a072..704100462f65 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -31,8 +32,10 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.ClassUtils; import static org.springframework.security.config.Customizer.withDefaults; @@ -40,7 +43,7 @@ /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Security when actuator is * on the classpath. It allows unauthenticated access to the {@link HealthEndpoint}. If - * the user specifies their own{@link SecurityFilterChain} bean, this will back-off + * the user specifies their own {@link SecurityFilterChain} bean, this will back-off * completely and the user should specify all the bits that they want to configure as part * of the custom security configuration. * @@ -58,9 +61,9 @@ public class ManagementWebSecurityAutoConfiguration { @Bean @Order(SecurityProperties.BASIC_AUTH_ORDER) - SecurityFilterChain managementSecurityFilterChain(HttpSecurity http) throws Exception { + SecurityFilterChain managementSecurityFilterChain(Environment environment, HttpSecurity http) throws Exception { http.authorizeHttpRequests((requests) -> { - requests.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll(); + requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); requests.anyRequest().authenticated(); }); if (ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) { @@ -71,4 +74,12 @@ SecurityFilterChain managementSecurityFilterChain(HttpSecurity http) throws Exce return http.build(); } + private RequestMatcher healthMatcher() { + return EndpointRequest.to(HealthEndpoint.class); + } + + private RequestMatcher additionalHealthPathsMatcher() { + return EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, HealthEndpoint.class); + } + } 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..a9aae6385d03 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) -@ConditionalOnAvailableEndpoint(endpoint = SessionsEndpoint.class) +@ConditionalOnClass(Session.class) +@ConditionalOnAvailableEndpoint(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/ssl/SslHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.java new file mode 100644 index 000000000000..b8f9f95f4320 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.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.actuate.autoconfigure.ssl; + +import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.ssl.SslHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.info.SslInfo; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link SslHealthIndicator}. + * + * @author Jonatan Ivanov + * @since 3.4.0 + */ +@AutoConfiguration(before = HealthContributorAutoConfiguration.class) +@ConditionalOnEnabledHealthIndicator("ssl") +@EnableConfigurationProperties(SslHealthIndicatorProperties.class) +public class SslHealthContributorAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "sslHealthIndicator") + SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) { + return new SslHealthIndicator(sslInfo); + } + + @Bean + @ConditionalOnMissingBean + SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) { + return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthIndicatorProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthIndicatorProperties.java new file mode 100644 index 000000000000..eb897d70eb09 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthIndicatorProperties.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.ssl; + +import java.time.Duration; + +import org.springframework.boot.actuate.ssl.SslHealthIndicator; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * External configuration properties for {@link SslHealthIndicator}. + * + * @author Jonatan Ivanov + * @since 3.4.0 + */ +@ConfigurationProperties(prefix = "management.health.ssl") +public class SslHealthIndicatorProperties { + + /** + * If an SSL Certificate will be invalid within the time span defined by this + * threshold, it should trigger a warning. + */ + private Duration certificateValidityWarningThreshold = Duration.ofDays(14); + + public Duration getCertificateValidityWarningThreshold() { + return this.certificateValidityWarningThreshold; + } + + public void setCertificateValidityWarningThreshold(Duration certificateValidityWarningThreshold) { + this.certificateValidityWarningThreshold = certificateValidityWarningThreshold; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/package-info.java new file mode 100644 index 000000000000..bfeaa736f6bf --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/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 ssl concerns. + */ +package org.springframework.boot.actuate.autoconfigure.ssl; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/startup/StartupEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/startup/StartupEndpointAutoConfiguration.java index 4871ee6d9f97..e7252e11a181 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/startup/StartupEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/startup/StartupEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ * @since 2.4.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = StartupEndpoint.class) +@ConditionalOnAvailableEndpoint(StartupEndpoint.class) @Conditional(StartupEndpointAutoConfiguration.ApplicationStartupCondition.class) public class StartupEndpointAutoConfiguration { 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 b1fbb798c6da..6db34e400919 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. @@ -70,13 +70,17 @@ BravePropagationConfigurations.NoPropagation.class }) public class BraveAutoConfiguration { - 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) @@ -88,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"); } @@ -112,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); @@ -125,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); @@ -143,14 +147,16 @@ public CurrentTraceContext braveCurrentTraceContext(List customizer.customize(throwAwayBuilder)); CompositePropagationFactory propagationFactory = CompositePropagationFactory.create( - this.tracingProperties.getPropagation(), BraveAutoConfiguration.BRAVE_BAGGAGE_MANAGER, + this.tracingProperties.getPropagation(), + new BraveBaggageManager(this.tracingProperties.getBaggage().getTagFields(), + this.tracingProperties.getBaggage().getRemoteFields()), 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(brave.propagation.Propagation.KeyFactory keyFactory) { + public Propagation get() { return null; } @@ -109,13 +111,16 @@ public Propagation create(brave.propagation.Propagation.KeyFactory key } @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))); } + List localFields = this.tracingProperties.getBaggage().getLocalFields(); + for (String localFieldName : localFields) { + builder.add(BaggagePropagationConfig.SingleBaggageField.local(BaggageField.create(localFieldName))); + } }; } 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 14ec2aea1e66..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. @@ -22,7 +22,6 @@ 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; @@ -71,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 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/MicrometerTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java index b53699f2c1f4..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 @@ -16,6 +16,7 @@ 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; @@ -29,7 +30,7 @@ import io.micrometer.tracing.propagation.Propagator; import org.aspectj.weaver.Advice; -import org.springframework.beans.factory.ObjectProvider; +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; @@ -42,6 +43,10 @@ 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. @@ -108,14 +113,18 @@ 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, ObjectProvider spanTagAnnotationHandler) { - ImperativeMethodInvocationProcessor methodInvocationProcessor = new ImperativeMethodInvocationProcessor( - newSpanParser, tracer); - spanTagAnnotationHandler.ifAvailable(methodInvocationProcessor::setSpanTagAnnotationHandler); - return methodInvocationProcessor; + Tracer tracer, SpanTagAnnotationHandler spanTagAnnotationHandler) { + return new ImperativeMethodInvocationProcessor(newSpanParser, tracer, spanTagAnnotationHandler); } @Bean @@ -126,6 +135,23 @@ SpanAspect spanAspect(MethodInvocationProcessor 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() { 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 6e5de4b51c6c..cfe3153332b1 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,47 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing; -import java.util.Collections; -import java.util.List; - -import io.micrometer.tracing.SpanCustomizer; -import io.micrometer.tracing.exporter.SpanExportingPredicate; -import io.micrometer.tracing.exporter.SpanFilter; -import io.micrometer.tracing.exporter.SpanReporter; -import io.micrometer.tracing.otel.bridge.CompositeSpanExporter; -import io.micrometer.tracing.otel.bridge.EventListener; -import io.micrometer.tracing.otel.bridge.EventPublishingContextWrapper; -import io.micrometer.tracing.otel.bridge.OtelBaggageManager; -import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; -import io.micrometer.tracing.otel.bridge.OtelPropagator; -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.Slf4JEventListener; -import io.opentelemetry.api.OpenTelemetry; -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.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 org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.SpringBootVersion; -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 OpenTelemetry tracing. @@ -65,129 +25,9 @@ * @author Marcin Grzejszczak * @author Yanming Zhou * @since 3.0.0 + * @deprecated since 3.4.0 in favor of {@link OpenTelemetryTracingAutoConfiguration} */ -@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 }) +@Deprecated(since = "3.4.0", forRemoval = true) public class OpenTelemetryAutoConfiguration { - private final TracingProperties tracingProperties; - - OpenTelemetryAutoConfiguration(TracingProperties tracingProperties) { - this.tracingProperties = tracingProperties; - } - - @Bean - @ConditionalOnMissingBean - 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(); - } - - @Bean - @ConditionalOnMissingBean - ContextPropagators otelContextPropagators(ObjectProvider textMapPropagators) { - return ContextPropagators.create(TextMapPropagator.composite(textMapPropagators.orderedStream().toList())); - } - - @Bean - @ConditionalOnMissingBean - Sampler otelSampler() { - Sampler rootSampler = Sampler.traceIdRatioBased(this.tracingProperties.getSampling().getProbability()); - return Sampler.parentBased(rootSampler); - } - - @Bean - @ConditionalOnMissingBean - SpanProcessors spanProcessors(ObjectProvider spanProcessors) { - return SpanProcessors.of(spanProcessors.orderedStream().toList()); - } - - @Bean - BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters, - ObjectProvider spanExportingPredicates, ObjectProvider spanReporters, - 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 - @ConditionalOnMissingBean - Tracer otelTracer(OpenTelemetry openTelemetry) { - return openTelemetry.getTracer("org.springframework.boot", SpringBootVersion.getVersion()); - } - - @Bean - @ConditionalOnMissingBean(io.micrometer.tracing.Tracer.class) - OtelTracer micrometerOtelTracer(Tracer tracer, EventPublisher eventPublisher, - OtelCurrentTraceContext otelCurrentTraceContext) { - return new OtelTracer(tracer, otelCurrentTraceContext, eventPublisher, - new OtelBaggageManager(otelCurrentTraceContext, this.tracingProperties.getBaggage().getRemoteFields(), - Collections.emptyList())); - } - - @Bean - @ConditionalOnMissingBean - OtelPropagator otelPropagator(ContextPropagators contextPropagators, Tracer tracer) { - return new OtelPropagator(contextPropagators, tracer); - } - - @Bean - @ConditionalOnMissingBean - EventPublisher otelTracerEventPublisher(List eventListeners) { - return new OTelEventPublisher(eventListeners); - } - - @Bean - @ConditionalOnMissingBean - OtelCurrentTraceContext otelCurrentTraceContext(EventPublisher publisher) { - ContextStorage.addWrapper(new EventPublishingContextWrapper(publisher)); - return new OtelCurrentTraceContext(); - } - - @Bean - @ConditionalOnMissingBean - Slf4JEventListener otelSlf4JEventListener() { - return new Slf4JEventListener(); - } - - @Bean - @ConditionalOnMissingBean(SpanCustomizer.class) - OtelSpanCustomizer otelSpanCustomizer() { - return new OtelSpanCustomizer(); - } - - static class OTelEventPublisher implements EventPublisher { - - private final List listeners; - - OTelEventPublisher(List listeners) { - this.listeners = listeners; - } - - @Override - public void publishEvent(Object event) { - for (EventListener listener : this.listeners) { - listener.onEvent(event); - } - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublisherBeansApplicationListener.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublisherBeansApplicationListener.java new file mode 100644 index 000000000000..724bb850fd91 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublisherBeansApplicationListener.java @@ -0,0 +1,201 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.concurrent.atomic.AtomicBoolean; + +import io.micrometer.tracing.otel.bridge.EventPublishingContextWrapper; +import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextStorage; +import io.opentelemetry.context.Scope; + +import org.springframework.boot.context.event.ApplicationStartingEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.GenericApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; +import org.springframework.util.ClassUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * {@link ApplicationListener} to add an OpenTelemetry {@link ContextStorage} wrapper for + * {@link EventPublisher} bean support. A single {@link ContextStorage} wrapper is added + * on the {@link ApplicationStartingEvent} then updated with {@link EventPublisher} beans + * as needed. + *

+ * The {@link #addWrapper()} method may also be called directly if the + * {@link ApplicationStartingEvent} isn't called early enough or isn't fired. + * + * @author Phillip Webb + * @since 3.4.0 + * @see OpenTelemetryEventPublisherBeansTestExecutionListener + */ +public class OpenTelemetryEventPublisherBeansApplicationListener implements GenericApplicationListener { + + private static final boolean OTEL_CONTEXT_PRESENT = ClassUtils.isPresent("io.opentelemetry.context.ContextStorage", + null); + + private static final boolean MICROMETER_OTEL_PRESENT = ClassUtils + .isPresent("io.micrometer.tracing.otel.bridge.OtelTracer", null); + + private static final AtomicBoolean added = new AtomicBoolean(); + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public boolean supportsEventType(ResolvableType eventType) { + Class type = eventType.getRawClass(); + return (type != null) && (ApplicationStartingEvent.class.isAssignableFrom(type) + || ContextRefreshedEvent.class.isAssignableFrom(type) + || ContextClosedEvent.class.isAssignableFrom(type)); + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (!isInstallable()) { + return; + } + if (event instanceof ApplicationStartingEvent) { + addWrapper(); + } + if (event instanceof ContextRefreshedEvent contextRefreshedEvent) { + ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext(); + List publishers = applicationContext + .getBeansOfType(EventPublisher.class, true, false) + .values() + .stream() + .map(EventPublishingContextWrapper::new) + .toList(); + Wrapper.instance.put(applicationContext, publishers); + } + if (event instanceof ContextClosedEvent contextClosedEvent) { + Wrapper.instance.remove(contextClosedEvent.getApplicationContext()); + } + } + + /** + * {@link ContextStorage#addWrapper(java.util.function.Function) Add} the + * {@link ContextStorage} wrapper to ensure that {@link EventPublisher + * EventPublishers} are propagated correctly. + */ + public static void addWrapper() { + if (isInstallable() && added.compareAndSet(false, true)) { + Wrapper.instance.addWrapper(); + } + } + + private static boolean isInstallable() { + return OTEL_CONTEXT_PRESENT && MICROMETER_OTEL_PRESENT; + } + + /** + * Single instance class used to add the wrapper and manage the {@link EventPublisher} + * beans. + */ + static final class Wrapper { + + static final Wrapper instance = new Wrapper(); + + private final MultiValueMap beans = new LinkedMultiValueMap<>(); + + private volatile ContextStorage storageDelegate; + + private Wrapper() { + } + + private void addWrapper() { + ContextStorage.addWrapper(Storage::new); + } + + void put(ApplicationContext applicationContext, List publishers) { + synchronized (this) { + this.beans.addAll(applicationContext, publishers); + this.storageDelegate = null; + } + } + + void remove(ApplicationContext applicationContext) { + synchronized (this) { + this.beans.remove(applicationContext); + this.storageDelegate = null; + } + } + + ContextStorage getStorageDelegate(ContextStorage parent) { + ContextStorage delegate = this.storageDelegate; + if (delegate == null) { + synchronized (this) { + delegate = this.storageDelegate; + if (delegate == null) { + delegate = parent; + for (List publishers : this.beans.values()) { + for (EventPublishingContextWrapper publisher : publishers) { + delegate = publisher.apply(delegate); + } + } + this.storageDelegate = delegate; + } + } + } + return delegate; + } + + /** + * {@link ContextStorage} that delegates to the {@link EventPublisher} beans. + */ + class Storage implements ContextStorage { + + private final ContextStorage parent; + + Storage(ContextStorage parent) { + this.parent = parent; + } + + @Override + public Scope attach(Context toAttach) { + return getDelegate().attach(toAttach); + } + + @Override + public Context current() { + return getDelegate().current(); + } + + @Override + public Context root() { + return getDelegate().root(); + } + + private ContextStorage getDelegate() { + return getStorageDelegate(this.parent); + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublisherBeansTestExecutionListener.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublisherBeansTestExecutionListener.java new file mode 100644 index 000000000000..f364a6a9e238 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublisherBeansTestExecutionListener.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.tracing; + +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; + +/** + * JUnit {@link TestExecutionListener} to ensure + * {@link OpenTelemetryEventPublisherBeansApplicationListener#addWrapper()} is called as + * early as possible. + * + * @author Phillip Webb + * @since 3.4.0 + * @see OpenTelemetryEventPublisherBeansApplicationListener + */ +public class OpenTelemetryEventPublisherBeansTestExecutionListener implements TestExecutionListener { + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + OpenTelemetryEventPublisherBeansApplicationListener.addWrapper(); + } + +} 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 index 4b9fcad16613..cb2210ec516e 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.otel.bridge.OtelBaggageManager; @@ -33,7 +32,7 @@ /** * OpenTelemetry propagation configurations. They are imported by - * {@link OpenTelemetryAutoConfiguration}. + * {@link OpenTelemetryTracingAutoConfiguration}. * * @author Moritz Halbritter */ @@ -73,8 +72,9 @@ static class PropagationWithBaggage { @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, Collections.emptyList())); + new OtelBaggageManager(otelCurrentTraceContext, remoteFields, tagFields)); return CompositeTextMapPropagator.create(this.tracingProperties.getPropagation(), baggagePropagator); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfiguration.java new file mode 100644 index 000000000000..3f88a7009ff3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfiguration.java @@ -0,0 +1,197 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.SpanCustomizer; +import io.micrometer.tracing.exporter.SpanExportingPredicate; +import io.micrometer.tracing.exporter.SpanFilter; +import io.micrometer.tracing.exporter.SpanReporter; +import io.micrometer.tracing.otel.bridge.CompositeSpanExporter; +import io.micrometer.tracing.otel.bridge.EventListener; +import io.micrometer.tracing.otel.bridge.OtelBaggageManager; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelPropagator; +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.Slf4JEventListener; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.SpringBootVersion; +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; +import org.springframework.util.CollectionUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry tracing. + * + * @author Moritz Halbritter + * @author Marcin Grzejszczak + * @author Yanming Zhou + * @since 3.4.0 + */ +@AutoConfiguration(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 OpenTelemetryTracingAutoConfiguration { + + private static final Log logger = LogFactory.getLog(OpenTelemetryTracingAutoConfiguration.class); + + private final TracingProperties tracingProperties; + + OpenTelemetryTracingAutoConfiguration(TracingProperties tracingProperties) { + this.tracingProperties = tracingProperties; + if (!CollectionUtils.isEmpty(this.tracingProperties.getBaggage().getLocalFields())) { + logger.warn("Local fields are not supported when using OpenTelemetry!"); + } + } + + @Bean + @ConditionalOnMissingBean + 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(); + } + + @Bean + @ConditionalOnMissingBean + ContextPropagators otelContextPropagators(ObjectProvider textMapPropagators) { + return ContextPropagators.create(TextMapPropagator.composite(textMapPropagators.orderedStream().toList())); + } + + @Bean + @ConditionalOnMissingBean + Sampler otelSampler() { + Sampler rootSampler = Sampler.traceIdRatioBased(this.tracingProperties.getSampling().getProbability()); + return Sampler.parentBased(rootSampler); + } + + @Bean + @ConditionalOnMissingBean + SpanProcessors spanProcessors(ObjectProvider spanProcessors) { + return SpanProcessors.of(spanProcessors.orderedStream().toList()); + } + + @Bean + BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters, + ObjectProvider spanExportingPredicates, ObjectProvider spanReporters, + 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 + @ConditionalOnMissingBean + Tracer otelTracer(OpenTelemetry openTelemetry) { + return openTelemetry.getTracer("org.springframework.boot", SpringBootVersion.getVersion()); + } + + @Bean + @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, remoteFields, tagFields)); + } + + @Bean + @ConditionalOnMissingBean + OtelPropagator otelPropagator(ContextPropagators contextPropagators, Tracer tracer) { + return new OtelPropagator(contextPropagators, tracer); + } + + @Bean + @ConditionalOnMissingBean + EventPublisher otelTracerEventPublisher(List eventListeners) { + return new OTelEventPublisher(eventListeners); + } + + @Bean + @ConditionalOnMissingBean + OtelCurrentTraceContext otelCurrentTraceContext() { + return new OtelCurrentTraceContext(); + } + + @Bean + @ConditionalOnMissingBean + Slf4JEventListener otelSlf4JEventListener() { + return new Slf4JEventListener(); + } + + @Bean + @ConditionalOnMissingBean(SpanCustomizer.class) + OtelSpanCustomizer otelSpanCustomizer() { + return new OtelSpanCustomizer(); + } + + static class OTelEventPublisher implements EventPublisher { + + private final List listeners; + + OTelEventPublisher(List listeners) { + this.listeners = listeners; + } + + @Override + public void publishEvent(Object event) { + for (EventListener listener : this.listeners) { + listener.onEvent(event); + } + } + + } + +} 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 9d02add3482e..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 { /** 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 abb3253f2f99..1860944dc53e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,38 +16,18 @@ package org.springframework.boot.actuate.autoconfigure.tracing.otlp; -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.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.sdk.trace.SdkTracerProvider; - -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. Brave does not support - * OTLP, so we only configure it for OpenTelemetry. OTLP defines three transports that are - * supported: gRPC (/protobuf), HTTP/protobuf, HTTP/JSON. From these transports HTTP/JSON - * is not supported by the OTel Java SDK, and it seems there are no plans supporting it in - * the future, see: opentelemetry-java#3651. - * Because this class configures components from the OTel SDK, it can't support HTTP/JSON. - * To keep things simple, we only auto-configure HTTP/protobuf. If you want to use gRPC, - * define an {@link OtlpGrpcSpanExporter} and this auto-configuration will back off. + * {@link EnableAutoConfiguration Auto-configuration} for exporting traces with OTLP. * * @author Jonatan Ivanov * @author Moritz Halbritter * @author Eddú Meléndez * @since 3.1.0 + * @deprecated since 3.4.0 in favor of {@link OtlpTracingAutoConfiguration} */ -@AutoConfiguration -@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class, OtlpHttpSpanExporter.class }) -@EnableConfigurationProperties(OtlpProperties.class) -@Import({ OtlpTracingConfigurations.ConnectionDetails.class, OtlpTracingConfigurations.Exporters.class }) +@Deprecated(since = "3.4.0", forRemoval = true) public class OtlpAutoConfiguration { } 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 deleted file mode 100644 index 371de8491146..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java +++ /dev/null @@ -1,103 +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.tracing.otlp; - -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for exporting traces using OTLP. - * - * @author Jonatan Ivanov - * @since 3.1.0 - */ -@ConfigurationProperties("management.otlp.tracing") -public class OtlpProperties { - - /** - * 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 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 void setHeaders(Map headers) { - this.headers = headers; - } - - 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/tracing/otlp/OtlpTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfiguration.java new file mode 100644 index 000000000000..d3677554b266 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfiguration.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.autoconfigure.tracing.otlp; + +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.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; + +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 exporting traces with OTLP. + * Brave does not support OTLP, so we only configure it for OpenTelemetry. OTLP defines + * three transports that are supported: gRPC (/protobuf), HTTP/protobuf, HTTP/JSON. From + * these transports HTTP/JSON is not supported by the OTel Java SDK, and it seems there + * are no plans supporting it in the future, see: opentelemetry-java#3651. + * Because this class configures components from the OTel SDK, it can't support HTTP/JSON. + * By default, we auto-configure HTTP/protobuf. If you want to use gRPC, you need to set + * {@code management.otlp.tracing.transport=grpc}. If you define a + * {@link OtlpHttpSpanExporter} or {@link OtlpGrpcSpanExporter}, this auto-configuration + * will back off. + * + * @author Jonatan Ivanov + * @author Moritz Halbritter + * @author Eddú Meléndez + * @since 3.4.0 + */ +@AutoConfiguration +@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class, OtlpHttpSpanExporter.class }) +@EnableConfigurationProperties(OtlpTracingProperties.class) +@Import({ OtlpTracingConfigurations.ConnectionDetails.class, OtlpTracingConfigurations.Exporters.class }) +public class OtlpTracingAutoConfiguration { + +} 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 index 9adac14d696f..6f0494379b8a 100644 --- 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 @@ -17,10 +17,11 @@ package org.springframework.boot.actuate.autoconfigure.tracing.otlp; import java.util.Locale; -import java.util.Map.Entry; 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.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -28,11 +29,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; /** - * Configurations imported by {@link OtlpAutoConfiguration}. + * Configurations imported by {@link OtlpTracingAutoConfiguration}. * * @author Moritz Halbritter + * @author Eddú Meléndez */ class OtlpTracingConfigurations { @@ -42,23 +45,26 @@ static class ConnectionDetails { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "endpoint") - OtlpTracingConnectionDetails otlpTracingConnectionDetails(OtlpProperties properties) { + OtlpTracingConnectionDetails otlpTracingConnectionDetails(OtlpTracingProperties properties) { return new PropertiesOtlpTracingConnectionDetails(properties); } /** - * Adapts {@link OtlpProperties} to {@link OtlpTracingConnectionDetails}. + * Adapts {@link OtlpTracingProperties} to {@link OtlpTracingConnectionDetails}. */ static class PropertiesOtlpTracingConnectionDetails implements OtlpTracingConnectionDetails { - private final OtlpProperties properties; + private final OtlpTracingProperties properties; - PropertiesOtlpTracingConnectionDetails(OtlpProperties properties) { + PropertiesOtlpTracingConnectionDetails(OtlpTracingProperties properties) { this.properties = properties; } @Override - public String getUrl() { + public String getUrl(Transport transport) { + Assert.state(transport == this.properties.getTransport(), + "Requested transport %s doesn't match configured transport %s".formatted(transport, + this.properties.getTransport())); return this.properties.getEndpoint(); } @@ -67,22 +73,35 @@ public String getUrl() { } @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean({ OtlpGrpcSpanExporter.class, OtlpHttpSpanExporter.class }) + @ConditionalOnBean(OtlpTracingConnectionDetails.class) + @ConditionalOnEnabledTracing("otlp") static class Exporters { @Bean - @ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class, - type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter") - @ConditionalOnBean(OtlpTracingConnectionDetails.class) - @ConditionalOnEnabledTracing - OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties, + @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "transport", havingValue = "http", + matchIfMissing = true) + OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpTracingProperties properties, OtlpTracingConnectionDetails connectionDetails) { OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder() - .setEndpoint(connectionDetails.getUrl()) + .setEndpoint(connectionDetails.getUrl(Transport.HTTP)) .setTimeout(properties.getTimeout()) + .setConnectTimeout(properties.getConnectTimeout()) .setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT)); - for (Entry header : properties.getHeaders().entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } + properties.getHeaders().forEach(builder::addHeader); + return builder.build(); + } + + @Bean + @ConditionalOnProperty(prefix = "management.otlp.tracing", name = "transport", havingValue = "grpc") + OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpTracingProperties properties, + OtlpTracingConnectionDetails connectionDetails) { + OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder() + .setEndpoint(connectionDetails.getUrl(Transport.GRPC)) + .setTimeout(properties.getTimeout()) + .setConnectTimeout(properties.getConnectTimeout()) + .setCompression(properties.getCompression().name().toLowerCase(Locale.ROOT)); + 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/tracing/otlp/OtlpTracingConnectionDetails.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingConnectionDetails.java index a84b11d64da3..bb50e4e7bc86 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ * Details required to establish a connection to an OpenTelemetry service. * * @author Eddú Meléndez + * @author Moritz Halbritter * @since 3.2.0 */ public interface OtlpTracingConnectionDetails extends ConnectionDetails { @@ -29,7 +30,19 @@ public interface OtlpTracingConnectionDetails extends ConnectionDetails { /** * Address to where tracing will be published. * @return the address to where tracing will be published + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of {@link #getUrl(Transport)} */ - String getUrl(); + @Deprecated(since = "3.4.0", forRemoval = true) + default String getUrl() { + return getUrl(Transport.HTTP); + } + + /** + * Address to where tracing will be published. + * @param transport the transport to use + * @return the address to where tracing will be published + * @since 3.4.0 + */ + String getUrl(Transport transport); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingProperties.java new file mode 100644 index 000000000000..6ebbdb2cf7b6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingProperties.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.otlp; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for exporting traces using OTLP. + * + * @author Jonatan Ivanov + * @since 3.1.0 + */ +@ConfigurationProperties("management.otlp.tracing") +public class OtlpTracingProperties { + + /** + * 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); + + /** + * Connect timeout for the OTel collector connection. + */ + private Duration connectTimeout = Duration.ofSeconds(10); + + /** + * Transport used to send the spans. + */ + private Transport transport = Transport.HTTP; + + /** + * 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 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 Duration getConnectTimeout() { + return this.connectTimeout; + } + + public void setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public Transport getTransport() { + return this.transport; + } + + public void setTransport(Transport transport) { + this.transport = transport; + } + + public Compression getCompression() { + return this.compression; + } + + public void setCompression(Compression compression) { + this.compression = compression; + } + + public Map getHeaders() { + return this.headers; + } + + public void setHeaders(Map headers) { + this.headers = 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/tracing/otlp/Transport.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/Transport.java new file mode 100644 index 000000000000..470a11e26f98 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/Transport.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.otlp; + +/** + * Transport used to send OTLP data. + * + * @author Moritz Halbritter + * @since 3.4.0 + */ +public enum Transport { + + /** + * HTTP transport. + */ + HTTP, + + /** + * gRPC transport. + */ + GRPC + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java index b021e3da9fca..7a5e2abf5d8e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for tracing with OTLP. + * Auto-configuration for exporting traces with OTLP. */ package org.springframework.boot.actuate.autoconfigure.tracing.otlp; 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 4176d2c2462b..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.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; @@ -41,42 +41,42 @@ @AutoConfiguration(before = PrometheusMetricsExportAutoConfiguration.class, after = MicrometerTracingAutoConfiguration.class) @ConditionalOnBean(Tracer.class) -@ConditionalOnClass({ Tracer.class, SpanContextSupplier.class }) +@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; @@ -85,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 f3fab744bcc4..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. @@ -59,7 +59,7 @@ public class WavefrontTracingAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(WavefrontSender.class) - @ConditionalOnEnabledTracing + @ConditionalOnEnabledTracing("wavefront") WavefrontSpanHandler wavefrontSpanHandler(WavefrontProperties properties, WavefrontSender wavefrontSender, SpanMetrics spanMetrics, ApplicationTags applicationTags) { return new WavefrontSpanHandler(properties.getSender().getMaxQueueSize(), wavefrontSender, spanMetrics, @@ -96,7 +96,7 @@ static class WavefrontBrave { @Bean @ConditionalOnMissingBean - @ConditionalOnEnabledTracing + @ConditionalOnEnabledTracing("wavefront") WavefrontBraveSpanHandler wavefrontBraveSpanHandler(WavefrontSpanHandler wavefrontSpanHandler) { return new WavefrontBraveSpanHandler(wavefrontSpanHandler); } @@ -109,7 +109,7 @@ static class WavefrontOpenTelemetry { @Bean @ConditionalOnMissingBean - @ConditionalOnEnabledTracing + @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..2d0b6db80c6e 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,76 @@ 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.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; 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; - } - catch (IOException | RuntimeException ex) { - return CheckResult.failed(ex); + protected void postSpans(URI endpoint, byte[] body) throws IOException { + MultiValueMap headers = getDefaultHeaders(); + if (needsCompression(body)) { + body = compress(body); + headers.add("Content-Encoding", "gzip"); } + postSpans(endpoint, headers, body); } - @Override - public void close() throws IOException { - this.closed = true; - } + abstract void postSpans(URI endpoint, MultiValueMap headers, byte[] body) throws IOException; - /** - * 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 - */ - protected abstract HttpPostCall sendSpans(byte[] batchedEncodedSpans); - - @Override - public Call sendSpans(List encodedSpans) { - if (this.closed) { - throw new ClosedSenderException(); - } - return sendSpans(BytesMessageEncoder.JSON.encode(encodedSpans)); + MultiValueMap getDefaultHeaders() { + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("b3", "0"); + headers.add("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 daff635f8631..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,14 +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.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; @@ -44,9 +40,8 @@ * @since 3.0.0 */ @AutoConfiguration(after = RestTemplateAutoConfiguration.class) -@ConditionalOnClass(Sender.class) -@Import({ SenderConfiguration.class, ReporterConfiguration.class, BraveConfiguration.class, - OpenTelemetryConfiguration.class }) +@ConditionalOnClass(Encoding.class) +@Import({ SenderConfiguration.class, BraveConfiguration.class, OpenTelemetryConfiguration.class }) @EnableConfigurationProperties(ZipkinProperties.class) public class ZipkinAutoConfiguration { @@ -58,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 f4ecc9503125..76ecc4cd830d 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,13 +16,22 @@ 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; @@ -48,7 +57,7 @@ class ZipkinConfigurations { @Configuration(proxyBeanMethods = false) @Import({ UrlConnectionSenderConfiguration.class, WebClientSenderConfiguration.class, - RestTemplateSenderConfiguration.class }) + RestTemplateSenderConfiguration.class, HttpClientSenderConfiguration.class }) static class SenderConfiguration { } @@ -59,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(); } @@ -79,19 +93,25 @@ URLConnectionSender urlConnectionSender(ZipkinProperties properties, static class RestTemplateSenderConfiguration { @Bean - @ConditionalOnMissingBean(Sender.class) - ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties, + @ConditionalOnMissingBean(BytesMessageSender.class) + @SuppressWarnings({ "deprecation", "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({ "deprecation", "removal" }) private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBuilder, ObjectProvider customizers) { Iterable orderedCustomizers = () -> customizers.orderedStream() @@ -111,56 +131,86 @@ private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBui static class WebClientSenderConfiguration { @Bean - @ConditionalOnMissingBean(Sender.class) - ZipkinWebClientSender webClientSender(ZipkinProperties properties, + @ConditionalOnMissingBean(BytesMessageSender.class) + @SuppressWarnings({ "deprecation", "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(), - properties.getConnectTimeout().plus(properties.getReadTimeout())); + 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) - @ConditionalOnEnabledTracing - 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) - @ConditionalOnEnabledTracing - 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..95d475f2d770 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.Factory; + 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 Factory 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..240d2f9e98a3 --- /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.Builder; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link Builder 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(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..8fa737f78c81 --- /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.util.MultiValueMap; + +/** + * 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, MultiValueMap 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..88bf8d5aec7d 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,11 +16,14 @@ 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.HttpMethod; +import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; /** @@ -28,58 +31,23 @@ * * @author Moritz Halbritter * @author Stefan Bratanov + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of {@link ZipkinHttpClientSender} */ +@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, MultiValueMap 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 2ef8cb74c09a..8ded275a6dfb 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,14 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.tracing.zipkin; +import java.net.URI; import java.time.Duration; -import reactor.core.publisher.Mono; -import zipkin2.Call; -import zipkin2.Callback; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.HttpEndpointSupplier.Factory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.client.WebClient; /** @@ -31,71 +30,32 @@ * * @author Stefan Bratanov * @author Moritz Halbritter + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of {@link ZipkinHttpClientSender} */ +@Deprecated(since = "3.3.0", forRemoval = true) class ZipkinWebClientSender extends HttpSender { - private final String endpoint; - private final WebClient webClient; private final Duration timeout; - ZipkinWebClientSender(String endpoint, WebClient webClient, Duration timeout) { - this.endpoint = endpoint; + 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, this.timeout); - } - - private static class WebClientHttpPostCall extends HttpPostCall { - - private final String endpoint; - - private final WebClient webClient; - - private final Duration timeout; - - WebClientHttpPostCall(String endpoint, byte[] body, WebClient webClient, Duration timeout) { - super(body); - this.endpoint = endpoint; - this.webClient = webClient; - this.timeout = timeout; - } - - @Override - public Call clone() { - return new WebClientHttpPostCall(this.endpoint, getUncompressedBody(), this.webClient, this.timeout); - } - - @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() - .timeout(this.timeout); - } - - private void addDefaultHeaders(HttpHeaders headers) { - headers.addAll(getDefaultHeaders()); - } - + void postSpans(URI endpoint, MultiValueMap 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/WavefrontSenderConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontSenderConfiguration.java index f7ffa4d06929..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 +69,7 @@ static final class WavefrontTracingOrMetricsCondition extends AnyNestedCondition super(ConfigurationPhase.REGISTER_BEAN); } - @ConditionalOnEnabledTracing + @ConditionalOnEnabledTracing("wavefront") static class TracingCondition { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/exchanges/HttpExchangesEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/exchanges/HttpExchangesEndpointAutoConfiguration.java index 137d1b323440..d193717b1b29 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/exchanges/HttpExchangesEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/exchanges/HttpExchangesEndpointAutoConfiguration.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,7 +33,7 @@ * @since 3.0.0 */ @AutoConfiguration(after = HttpExchangesAutoConfiguration.class) -@ConditionalOnAvailableEndpoint(endpoint = HttpExchangesEndpoint.class) +@ConditionalOnAvailableEndpoint(HttpExchangesEndpoint.class) public class HttpExchangesEndpointAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/mappings/MappingsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/mappings/MappingsEndpointAutoConfiguration.java index a2aa5c174417..a96085d1e84b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/mappings/MappingsEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/mappings/MappingsEndpointAutoConfiguration.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 @@ * @since 2.0.0 */ @AutoConfiguration -@ConditionalOnAvailableEndpoint(endpoint = MappingsEndpoint.class) +@ConditionalOnAvailableEndpoint(MappingsEndpoint.class) public class MappingsEndpointAutoConfiguration { @Bean 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 e580d5dfcd1c..21de0fcad089 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.AnnotationConfigRegistry; import org.springframework.context.aot.ApplicationContextAotGenerator; -import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.DefaultResourceLoader; @@ -51,8 +50,8 @@ import org.springframework.util.Assert; /** - * {@link ApplicationListener} used to initialize the management context when it's running - * on a different port. + * {@link SmartLifecycle} used to initialize the management context when it's running on a + * different port. * * @author Andy Wilkinson * @author Phillip Webb @@ -61,20 +60,20 @@ class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, private final ManagementContextFactory managementContextFactory; - private final ApplicationContext parentContext; + private final AbstractApplicationContext parentContext; private final ApplicationContextInitializer applicationContextInitializer; private volatile ConfigurableApplicationContext managementContext; ChildManagementContextInitializer(ManagementContextFactory managementContextFactory, - ApplicationContext parentContext) { + AbstractApplicationContext parentContext) { this(managementContextFactory, parentContext, null); } @SuppressWarnings("unchecked") private ChildManagementContextInitializer(ManagementContextFactory managementContextFactory, - ApplicationContext parentContext, + AbstractApplicationContext parentContext, ApplicationContextInitializer applicationContextInitializer) { this.managementContextFactory = managementContextFactory; this.parentContext = parentContext; @@ -100,7 +99,12 @@ public void start() { @Override public void stop() { if (this.managementContext != null) { - this.managementContext.stop(); + if (this.parentContext.isClosed()) { + this.managementContext.close(); + } + else { + this.managementContext.stop(); + } } } @@ -111,7 +115,7 @@ public boolean isRunning() { @Override public int getPhase() { - return WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_PHASE + 512; + return WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_PHASE - 512; } @Override @@ -161,8 +165,7 @@ protected final ConfigurableApplicationContext createManagementContext() { } private boolean isLazyInitialization() { - AbstractApplicationContext context = (AbstractApplicationContext) this.parentContext; - List postProcessors = context.getBeanFactoryPostProcessors(); + List postProcessors = this.parentContext.getBeanFactoryPostProcessors(); return postProcessors.stream().anyMatch(LazyInitializationBeanFactoryPostProcessor.class::isInstance); } @@ -205,8 +208,8 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be } /** - * {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and - * {@link ApplicationFailedEvent} from a parent to a child. + * {@link ApplicationListener} to propagate the {@link ApplicationFailedEvent} from a + * parent to a child. */ private static class CloseManagementContextListener implements ApplicationListener { @@ -221,18 +224,11 @@ private static class CloseManagementContextListener implements ApplicationListen @Override public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextClosedEvent contextClosedEvent) { - onContextClosedEvent(contextClosedEvent); - } if (event instanceof ApplicationFailedEvent applicationFailedEvent) { onApplicationFailedEvent(applicationFailedEvent); } } - private void onContextClosedEvent(ContextClosedEvent event) { - propagateCloseIfNecessary(event.getApplicationContext()); - } - private void onApplicationFailedEvent(ApplicationFailedEvent event) { propagateCloseIfNecessary(event.getApplicationContext()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java index da3da45ad2bc..24b8de40e4e7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -112,7 +112,7 @@ static class DifferentManagementContextConfiguration { @Bean static ChildManagementContextInitializer childManagementContextInitializer( - ManagementContextFactory managementContextFactory, ApplicationContext parentContext) { + ManagementContextFactory managementContextFactory, AbstractApplicationContext parentContext) { return new ChildManagementContextInitializer(managementContextFactory, parentContext); } 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/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 9456c54af832..855ab8e56d45 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 @@ -30,14 +30,6 @@ "description": "Whether to enable default metrics exporters.", "defaultValue": true }, - { - "name": "management.endpoint.configprops.show-values", - "defaultValue": "never" - }, - { - "name": "management.endpoint.env.show-values", - "defaultValue": "never" - }, { "name": "management.endpoint.health.probes.add-additional-paths", "type": "java.lang.Boolean", @@ -50,10 +42,6 @@ "description": "Whether to enable liveness and readiness probes.", "defaultValue": false }, - { - "name": "management.endpoint.health.show-details", - "defaultValue": "never" - }, { "name": "management.endpoint.health.status.order", "defaultValue": [ @@ -70,13 +58,23 @@ "defaultValue": true }, { - "name": "management.endpoint.quartz.show-values", - "defaultValue": "never" + "name": "management.endpoints.access.default", + "type": "java.lang.Boolean", + "description": "Default access level for all endpoints." + }, + { + "name": "management.endpoints.access.max-permitted", + "description": "The maximum level of endpoint access that is permitted. Caps an endpoint's individual access level (management.endpoint..access) and the default access (management.endpoints.access.default).'", + "defaultValue": "unrestricted" }, { "name": "management.endpoints.enabled-by-default", "type": "java.lang.Boolean", - "description": "Whether to enable or disable all endpoints by default." + "description": "Whether to enable or disable all endpoints by default.", + "deprecation": { + "replacement": "management.endpoints.access.default", + "since": "3.4.0" + } }, { "name": "management.endpoints.jackson.isolated-object-mapper", @@ -107,26 +105,6 @@ "health" ] }, - { - "name": "management.ganglia.metrics.export.addressing-mode", - "defaultValue": "multicast" - }, - { - "name": "management.ganglia.metrics.export.duration-units", - "defaultValue": "milliseconds" - }, - { - "name": "management.graphite.metrics.export.duration-units", - "defaultValue": "milliseconds" - }, - { - "name": "management.graphite.metrics.export.protocol", - "defaultValue": "pickled" - }, - { - "name": "management.graphite.metrics.export.rate-units", - "defaultValue": "seconds" - }, { "name": "management.health.cassandra.enabled", "type": "java.lang.Boolean", @@ -263,6 +241,12 @@ "description": "Whether to enable Redis health check.", "defaultValue": true }, + { + "name": "management.health.ssl.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable SSL certificate health check.", + "defaultValue": true + }, { "name": "management.httpexchanges.recording.enabled", "type": "java.lang.Boolean", @@ -277,10 +261,6 @@ "errors" ] }, - { - "name": "management.influx.metrics.export.consistency", - "defaultValue": "one" - }, { "name": "management.info.build.enabled", "type": "java.lang.Boolean", @@ -305,10 +285,6 @@ "description": "Whether to enable git info.", "defaultValue": true }, - { - "name": "management.info.git.mode", - "defaultValue": "simple" - }, { "name": "management.info.java.enabled", "type": "java.lang.Boolean", @@ -321,6 +297,24 @@ "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.info.ssl.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable SSL certificate info.", + "defaultValue": false + }, + { + "name": "management.logging.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of logging is enabled to export logs.", + "defaultValue": true + }, { "name": "management.metrics.binders.files.enabled", "type": "java.lang.Boolean", @@ -2073,10 +2067,6 @@ "level": "error" } }, - { - "name": "management.newrelic.metrics.export.client-provider-type", - "defaultValue": "insights-api" - }, { "name": "management.observations.annotations.enabled", "type": "java.lang.Boolean", @@ -2084,24 +2074,14 @@ "defaultValue": false }, { - "name": "management.otlp.metrics.export.aggregation-temporality", - "defaultValue": "cumulative" - }, - { - "name": "management.otlp.metrics.export.base-time-unit", - "defaultValue": "milliseconds" - }, - { - "name": "management.otlp.tracing.compression", - "defaultValue": "none" - }, - { - "name": "management.prometheus.metrics.export.histogram-flavor", - "defaultValue": "prometheus" + "name": "management.otlp.logging.export.enabled", + "type": "java.lang.Boolean", + "description": "Whether auto-configuration of logging is enabled to export OTLP logs." }, { - "name": "management.prometheus.metrics.export.pushgateway.shutdown-operation", - "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.server.add-application-context-header", @@ -2175,6 +2155,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." @@ -2199,22 +2183,6 @@ "name": "management.server.ssl.trust-store-type", "description": "Type of the trust store." }, - { - "name": "management.signalfx.metrics.export.published-histogram-type", - "defaultValue": "default" - }, - { - "name": "management.simple.metrics.export.mode", - "defaultValue": "cumulative" - }, - { - "name": "management.statsd.metrics.export.flavor", - "defaultValue": "datadog" - }, - { - "name": "management.statsd.metrics.export.protocol", - "defaultValue": "udp" - }, { "name": "management.trace.http.enabled", "deprecation": { @@ -2256,6 +2224,22 @@ "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", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 000000000000..284014dde279 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1 @@ +org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryEventPublisherBeansTestExecutionListener 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 a32bb38f5a58..143d99335558 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,8 +1,16 @@ +# Endpoint Exposure Outcome Contributors +org.springframework.boot.actuate.autoconfigure.endpoint.condition.EndpointExposureOutcomeContributor=\ +org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryEndpointExposureOutcomeContributor + # 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 + +# Application Listeners +org.springframework.context.ApplicationListener=\ +org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryEventPublisherBeansApplicationListener 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 7801946776fc..56f12f9b44a5 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.OpenTelemetryLoggingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration org.springframework.boot.actuate.autoconfigure.mail.MailHealthContributorAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.HeapDumpWebEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration @@ -64,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 @@ -95,19 +97,22 @@ org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthCont 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 org.springframework.boot.actuate.autoconfigure.startup.StartupEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.ssl.SslHealthContributorAutoConfiguration 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.OpenTelemetryTracingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration 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 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements new file mode 100644 index 000000000000..86a848dac233 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements @@ -0,0 +1,2 @@ +org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration=org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration=org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java index 7c42682d9519..e64dadd38d7b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; import org.springframework.boot.availability.ApplicationAvailability; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -40,32 +41,23 @@ class AvailabilityProbesAutoConfigurationTests { @Test void probesWhenNotKubernetesAddsNoBeans() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) - .doesNotHaveBean(LivenessStateHealthIndicator.class) - .doesNotHaveBean(ReadinessStateHealthIndicator.class) - .doesNotHaveBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class)); + this.contextRunner.run(this::doesNotHaveProbeBeans); } @Test void probesWhenKubernetesAddsBeans() { - this.contextRunner.withPropertyValues("spring.main.cloud-platform=kubernetes") - .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) - .hasSingleBean(LivenessStateHealthIndicator.class) - .hasBean("livenessStateHealthIndicator") - .hasSingleBean(ReadinessStateHealthIndicator.class) - .hasBean("readinessStateHealthIndicator") - .hasSingleBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class)); + this.contextRunner.withPropertyValues("spring.main.cloud-platform=kubernetes").run(this::hasProbesBeans); + } + + @Test + void probesWhenCloudFoundryAddsBeans() { + this.contextRunner.withPropertyValues("spring.main.cloud-platform=cloud_foundry").run(this::hasProbesBeans); } @Test void probesWhenPropertyEnabledAddsBeans() { this.contextRunner.withPropertyValues("management.endpoint.health.probes.enabled=true") - .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) - .hasSingleBean(LivenessStateHealthIndicator.class) - .hasBean("livenessStateHealthIndicator") - .hasSingleBean(ReadinessStateHealthIndicator.class) - .hasBean("readinessStateHealthIndicator") - .hasSingleBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class)); + .run(this::hasProbesBeans); } @Test @@ -73,10 +65,23 @@ void probesWhenKubernetesAndPropertyDisabledAddsNotBeans() { this.contextRunner .withPropertyValues("spring.main.cloud-platform=kubernetes", "management.endpoint.health.probes.enabled=false") - .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) - .doesNotHaveBean(LivenessStateHealthIndicator.class) - .doesNotHaveBean(ReadinessStateHealthIndicator.class) - .doesNotHaveBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class)); + .run(this::doesNotHaveProbeBeans); + } + + private void hasProbesBeans(AssertableApplicationContext context) { + assertThat(context).hasSingleBean(ApplicationAvailability.class) + .hasSingleBean(LivenessStateHealthIndicator.class) + .hasBean("livenessStateHealthIndicator") + .hasSingleBean(ReadinessStateHealthIndicator.class) + .hasBean("readinessStateHealthIndicator") + .hasSingleBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class); + } + + private void doesNotHaveProbeBeans(AssertableApplicationContext context) { + assertThat(context).hasSingleBean(ApplicationAvailability.class) + .doesNotHaveBean(LivenessStateHealthIndicator.class) + .doesNotHaveBean(ReadinessStateHealthIndicator.class) + .doesNotHaveBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index 38b984a501d9..e412e76d1725 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,7 +105,8 @@ private void load(Function timeToLive, PathMapper endpointPath Collections.singletonList("application/json")); CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(context, parameterMapper, mediaTypes, Collections.singletonList(endpointPathMapper), - Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), Collections.emptyList()); + Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), Collections.emptyList(), + Collections.emptyList()); consumer.accept(discoverer); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java index 99bb7885735d..33d06aa6f79d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -262,8 +262,8 @@ WebEndpointDiscoverer webEndpointDiscoverer(ApplicationContext applicationContex EndpointMediaTypes endpointMediaTypes) { ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper( DefaultConversionService.getSharedInstance()); - return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, null, - Collections.emptyList(), Collections.emptyList()); + return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, null, null, + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } @Bean 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 067eea979426..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. @@ -54,15 +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}. @@ -109,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); }); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java index 09c2e72a25a9..81440fd0b610 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -256,8 +256,8 @@ WebEndpointDiscoverer webEndpointDiscoverer(ApplicationContext applicationContex EndpointMediaTypes endpointMediaTypes) { ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper( DefaultConversionService.getSharedInstance()); - return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, null, - Collections.emptyList(), Collections.emptyList()); + return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, null, null, + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/PropertiesEndpointAccessResolverTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/PropertiesEndpointAccessResolverTests.java new file mode 100644 index 000000000000..9907b4b712bb --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/PropertiesEndpointAccessResolverTests.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.actuate.autoconfigure.endpoint; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link PropertiesEndpointAccessResolver}. + * + * @author Andy Wilkinson + */ +class PropertiesEndpointAccessResolverTests { + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + void whenNoPropertiesAreConfiguredThenAccessForReturnsEndpointsDefaultAccess() { + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.READ_ONLY); + } + + @Test + void whenDefaultAccessForAllEndpointsIsConfiguredThenAccessForReturnsDefaultForAllEndpoints() { + this.environment.withProperty("management.endpoints.access.default", Access.UNRESTRICTED.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.UNRESTRICTED); + } + + @Test + void whenAccessForEndpointIsConfiguredThenAccessForReturnsIt() { + this.environment.withProperty("management.endpoint.test.access", Access.UNRESTRICTED.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.UNRESTRICTED); + } + + @Test + void whenAccessForEndpointAndDefaultAccessForAllEndpointsAreConfiguredAccessForReturnsAccessForEndpoint() { + this.environment.withProperty("management.endpoint.test.access", Access.NONE.name()) + .withProperty("management.endpoints.access.default", Access.UNRESTRICTED.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.NONE); + } + + @Test + void whenAllEndpointsAreDisabledByDefaultAccessForReturnsNone() { + this.environment.withProperty("management.endpoints.enabled-by-default", "false"); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.NONE); + } + + @Test + void whenAllEndpointsAreEnabledByDefaultAccessForReturnsUnrestricted() { + this.environment.withProperty("management.endpoints.enabled-by-default", "true"); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.UNRESTRICTED); + } + + @Test + void whenEndpointIsDisabledAccessForReturnsNone() { + this.environment.withProperty("management.endpoint.test.enabled", "false"); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.NONE); + } + + @Test + void whenEndpointIsEnabledAccessForReturnsUnrestricted() { + this.environment.withProperty("management.endpoint.test.enabled", "true"); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.UNRESTRICTED); + } + + @Test + void whenEnabledByDefaultAndDefaultAccessAreBothConfiguredResolverCreationThrows() { + this.environment.withProperty("management.endpoints.enabled-by-default", "true") + .withProperty("management.endpoints.access.default", Access.READ_ONLY.name()); + assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class) + .isThrownBy(this::accessResolver); + } + + @Test + void whenEndpointEnabledAndAccessAreBothConfiguredAccessForThrows() { + this.environment.withProperty("management.endpoint.test.enabled", "true") + .withProperty("management.endpoint.test.access", Access.READ_ONLY.name()); + assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class) + .isThrownBy(() -> accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)); + } + + @Test + void whenAllEndpointsAreEnabledByDefaultAndAccessIsLimitedToReadOnlyAccessForReturnsReadOnly() { + this.environment.withProperty("management.endpoints.enabled-by-default", "true") + .withProperty("management.endpoints.access.max-permitted", Access.READ_ONLY.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.READ_ONLY); + } + + @Test + void whenAllEndpointsHaveUnrestrictedDefaultAccessAndAccessIsLimitedToReadOnlyAccessForReturnsReadOnly() { + this.environment.withProperty("management.endpoints.access.default", Access.UNRESTRICTED.name()) + .withProperty("management.endpoints.access.max-permitted", Access.READ_ONLY.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.READ_ONLY); + } + + @Test + void whenEndpointsIsEnabledAndAccessIsLimitedToNoneAccessForReturnsNone() { + this.environment.withProperty("management.endpoint.test.enabled", "true") + .withProperty("management.endpoints.access.max-permitted", Access.NONE.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.NONE); + } + + @Test + void whenEndpointsHasUnrestrictedAccessAndAccessIsLimitedToNoneAccessForReturnsNone() { + this.environment.withProperty("management.endpoint.test.access", Access.UNRESTRICTED.name()) + .withProperty("management.endpoints.access.max-permitted", Access.NONE.name()); + assertThat(accessResolver().accessFor(EndpointId.of("test"), Access.READ_ONLY)).isEqualTo(Access.NONE); + } + + private PropertiesEndpointAccessResolver accessResolver() { + return new PropertiesEndpointAccessResolver(this.environment); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java index c19c434daec4..9636b5ac1f00 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; +import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,7 +40,9 @@ class ConditionalOnAvailableEndpointTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withUserConfiguration(AllEndpointsConfiguration.class); + .withUserConfiguration(AllEndpointsConfiguration.class) + .withInitializer( + (context) -> context.getEnvironment().setConversionService(new ApplicationConversionService())); @Test void outcomeShouldMatchDefaults() { @@ -252,6 +257,43 @@ void outcomeWhenEndpointNotExposedOnSpecifiedTechnology() { .run((context) -> assertThat(context).doesNotHaveBean("unexposed")); } + @Test + void whenBothAccessAndEnabledAreConfiguredThenThrows() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoint.shutdown.enabled=true", "management.endpoint.shutdown.access=none") + .run((context) -> assertThat(context).hasFailed() + .getFailure() + .rootCause() + .isInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class)); + } + + @Test + void whenBothDefaultAccessAndDefaultEnabledAreConfiguredThenThrows() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.enabled-by-default=true", "management.endpoints.access.default=none") + .run((context) -> assertThat(context).hasFailed() + .getFailure() + .rootCause() + .isInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class)); + } + + @Test + void whenDisabledAndAccessibleByDefaultEndpointIsNotAvailable() { + this.contextRunner.withUserConfiguration(DisabledButAccessibleEndpointConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=*") + .run((context) -> assertThat(context).doesNotHaveBean(DisabledButAccessibleEndpoint.class)); + } + + @Test + void whenDisabledAndAccessibleByDefaultEndpointCanBeAvailable() { + this.contextRunner.withUserConfiguration(DisabledButAccessibleEndpointConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=unrestricted") + .run((context) -> assertThat(context).hasSingleBean(DisabledButAccessibleEndpoint.class)); + } + @Endpoint(id = "health") static class HealthEndpoint { @@ -272,7 +314,7 @@ static class TestEndpoint { } - @Endpoint(id = "shutdown", enableByDefault = false) + @Endpoint(id = "shutdown", defaultAccess = Access.NONE) static class ShutdownEndpoint { } @@ -282,6 +324,11 @@ static class DashedEndpoint { } + @Endpoint(id = "disabledbutaccessible", enableByDefault = false) + static class DisabledButAccessibleEndpoint { + + } + @EndpointExtension(endpoint = SpringEndpoint.class, filter = TestFilter.class) static class SpringEndpointExtension { @@ -335,7 +382,7 @@ ShutdownEndpoint shutdown() { static class ComponentEnabledIfEndpointIsExposedConfiguration { @Bean - @ConditionalOnAvailableEndpoint(endpoint = SpringEndpoint.class) + @ConditionalOnAvailableEndpoint(SpringEndpoint.class) String springComponent() { return "springComponent"; } @@ -374,12 +421,22 @@ DashedEndpoint dashedEndpoint() { static class ExposureEndpointConfiguration { @Bean - @ConditionalOnAvailableEndpoint(endpoint = TestEndpoint.class, - exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) + @ConditionalOnAvailableEndpoint(endpoint = TestEndpoint.class, exposure = EndpointExposure.WEB) String unexposed() { return "unexposed"; } } + @Configuration(proxyBeanMethods = false) + static class DisabledButAccessibleEndpointConfiguration { + + @Bean + @ConditionalOnAvailableEndpoint + DisabledButAccessibleEndpoint disabledButAccessible() { + return new DisabledButAccessibleEndpoint(); + } + + } + } 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..bfaa7234cc9c 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. @@ -21,6 +21,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; @@ -42,6 +44,7 @@ * @author Phillip Webb * @author Madhura Bhave */ +@SuppressWarnings("removal") class ServletEndpointManagementContextConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() @@ -93,6 +96,11 @@ JerseyApplicationPath jerseyApplicationPath() { return () -> "/jersey"; } + @Bean + EndpointAccessResolver endpointAccessResolver() { + return (endpointId, defaultAccess) -> Access.UNRESTRICTED; + } + } } 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..38692f4b1235 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. @@ -27,12 +27,11 @@ import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -95,9 +94,11 @@ void webApplicationSupportCustomPathMatcher() { } @Test + @SuppressWarnings("removal") void webApplicationConfiguresEndpointDiscoverer() { this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(ControllerEndpointDiscoverer.class); + assertThat(context).hasSingleBean( + org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer.class); assertThat(context).hasSingleBean(WebEndpointDiscoverer.class); }); } @@ -109,14 +110,18 @@ void webApplicationConfiguresExposeExcludePropertyEndpointFilter() { } @Test + @SuppressWarnings("removal") void contextShouldConfigureServletEndpointDiscoverer() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ServletEndpointDiscoverer.class)); + this.contextRunner.run((context) -> assertThat(context) + .hasSingleBean(org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer.class)); } @Test + @SuppressWarnings("removal") void contextWhenNotServletShouldNotConfigureServletEndpointDiscoverer() { new ApplicationContextRunner().withConfiguration(CONFIGURATIONS) - .run((context) -> assertThat(context).doesNotHaveBean(ServletEndpointDiscoverer.class)); + .run((context) -> assertThat(context).doesNotHaveBean( + org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer.class)); } @Component @@ -136,18 +141,33 @@ public String getRootPath(EndpointId endpointId) { @Endpoint(id = "testone") static class TestOneEndpoint { + @ReadOperation + String read() { + return "read"; + } + } @Component @Endpoint(id = "testanotherone") static class TestAnotherOneEndpoint { + @ReadOperation + String read() { + return "read"; + } + } @Component @Endpoint(id = "testtwo") static class TestTwoEndpoint { + @ReadOperation + String read() { + return "read"; + } + } } 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..f00188109854 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}. @@ -45,7 +44,7 @@ class ConditionsReportEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test - void conditions() throws Exception { + void conditions() { List positiveMatchFields = List.of( fieldWithPath("").description("Classes and methods with conditions that were matched."), fieldWithPath(".*.[].condition").description("Name of the condition."), @@ -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..b5f504e844f3 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(), @@ -138,7 +138,7 @@ private byte[] filterProperties(byte[] content, MediaType mediaType) { } private boolean retainKey(String key) { - return key.startsWith("java.") || key.equals("JAVA_HOME") || key.startsWith("com.example"); + return key.startsWith("java.") || key.equals("JAVA_HOME") || key.startsWith("com.example."); } @Configuration(proxyBeanMethods = false) 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..c831adea9909 --- /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.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 org.springframework.boot.actuate.metrics.export.prometheus.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" }) + org.springframework.boot.actuate.metrics.export.prometheus.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 org.springframework.boot.actuate.metrics.export.prometheus.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..e63717bccd87 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 @@ -27,20 +27,21 @@ 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.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler; 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,18 +51,19 @@ 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")), responseFields(fieldWithPath("cron").description("Cron tasks, if any."), targetFieldWithPrefix("cron.[]."), + nextExecutionWithPrefix("cron.[].").description("Time of the next scheduled execution."), fieldWithPath("cron.[].expression").description("Cron expression."), fieldWithPath("fixedDelay").description("Fixed delay tasks, if any."), targetFieldWithPrefix("fixedDelay.[]."), initialDelayWithPrefix("fixedDelay.[]."), + nextExecutionWithPrefix("fixedDelay.[]."), fieldWithPath("fixedDelay.[].interval") .description("Interval, in milliseconds, between the end of the last" + " execution and the start of the next."), @@ -69,10 +71,15 @@ void scheduledTasks() throws Exception { targetFieldWithPrefix("fixedRate.[]."), fieldWithPath("fixedRate.[].interval") .description("Interval, in milliseconds, between the start of each execution."), - initialDelayWithPrefix("fixedRate.[]."), + initialDelayWithPrefix("fixedRate.[]."), nextExecutionWithPrefix("fixedRate.[]."), fieldWithPath("custom").description("Tasks with custom triggers, if any."), targetFieldWithPrefix("custom.[]."), - fieldWithPath("custom.[].trigger").description("Trigger for the task.")))); + fieldWithPath("custom.[].trigger").description("Trigger for the task.")) + .andWithPrefix("*.[].", + fieldWithPath("lastExecution").description("Last execution of this task, if any.") + .optional() + .type(JsonFieldType.OBJECT)) + .andWithPrefix("*.[].lastExecution.", lastExecution()))); } private FieldDescriptor targetFieldWithPrefix(String prefix) { @@ -83,6 +90,26 @@ private FieldDescriptor initialDelayWithPrefix(String prefix) { return fieldWithPath(prefix + "initialDelay").description("Delay, in milliseconds, before first execution."); } + private FieldDescriptor nextExecutionWithPrefix(String prefix) { + return fieldWithPath(prefix + "nextExecution.time") + .description("Time of the next scheduled execution, if known.") + .type(JsonFieldType.STRING) + .optional(); + } + + private FieldDescriptor[] lastExecution() { + return new FieldDescriptor[] { + fieldWithPath("status").description("Status of the last execution (STARTED, SUCCESS, ERROR).") + .type(JsonFieldType.STRING), + fieldWithPath("time").description("Time of the last execution.").type(JsonFieldType.STRING), + fieldWithPath("exception.type").description("Exception type thrown by the task, if any.") + .type(JsonFieldType.STRING) + .optional(), + fieldWithPath("exception.message").description("Message of the exception thrown by the task, if any.") + .type(JsonFieldType.STRING) + .optional() }; + } + @Configuration(proxyBeanMethods = false) @EnableScheduling @Import(BaseDocumentationConfiguration.class) @@ -98,7 +125,7 @@ void processOrders() { } - @Scheduled(fixedDelay = 5000, initialDelay = 5000) + @Scheduled(fixedDelay = 5000, initialDelay = 0) void purge() { } @@ -110,7 +137,10 @@ void retrieveIssues() { @Bean SchedulingConfigurer schedulingConfigurer() { - return (registrar) -> registrar.addTriggerTask(new CustomTriggeredRunnable(), new CustomTrigger()); + return (registrar) -> { + registrar.setTaskScheduler(new TestTaskScheduler()); + registrar.addTriggerTask(new CustomTriggeredRunnable(), new CustomTrigger()); + }; } static class CustomTrigger implements Trigger { @@ -126,7 +156,18 @@ static class CustomTriggeredRunnable implements Runnable { @Override public void run() { + throw new IllegalStateException("Failed while running custom task"); + } + + } + + static class TestTaskScheduler extends SimpleAsyncTaskScheduler { + TestTaskScheduler() { + setThreadNamePrefix("test-"); + // do not log task errors + setErrorHandler((throwable) -> { + }); } } 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..e4a742862bc8 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 @@ -24,24 +24,24 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.test.context.TestPropertySource; +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}. * * @author Andy Wilkinson */ +@TestPropertySource(properties = "management.endpoint.shutdown.access=unrestricted") 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/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java index 4a667841c390..50427a7ebff1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration.JerseyWebEndpointsResourcesRegistrar; import org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; @@ -43,7 +45,8 @@ class JerseyWebEndpointManagementContextConfigurationTests { private final WebApplicationContextRunner runner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class, JerseyWebEndpointManagementContextConfiguration.class)) - .withBean(WebEndpointsSupplier.class, () -> Collections::emptyList); + .withBean(WebEndpointsSupplier.class, () -> Collections::emptyList) + .withBean(EndpointAccessResolver.class, () -> (endpointId, defaultAccess) -> Access.UNRESTRICTED); @Test void jerseyWebEndpointsResourcesRegistrarForEndpointsIsAutoConfigured() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java index f967126a9dc7..c00790d80632 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroupsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; +import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.boot.actuate.health.HttpCodeStatusMapper; @@ -333,6 +337,24 @@ void createWhenGroupWithNoShowDetailsOverrideInheritsShowDetails() { }); } + @Test + void getAdditionalPathsReturnsAllAdditionalPaths() { + this.contextRunner + .withPropertyValues("management.endpoint.health.group.a.additional-path=server:/a", + "management.endpoint.health.group.b.additional-path=server:/b", + "management.endpoint.health.group.c.additional-path=management:/c", + "management.endpoint.health.group.d.additional-path=management:/d") + .run((context) -> { + AdditionalPathsMapper additionalPathsMapper = context.getBean(AdditionalPathsMapper.class); + assertThat(additionalPathsMapper.getAdditionalPaths(HealthEndpoint.ID, WebServerNamespace.SERVER)) + .containsExactlyInAnyOrder("/a", "/b"); + assertThat(additionalPathsMapper.getAdditionalPaths(HealthEndpoint.ID, WebServerNamespace.MANAGEMENT)) + .containsExactlyInAnyOrder("/c", "/d"); + assertThat(additionalPathsMapper.getAdditionalPaths(EndpointId.of("other"), WebServerNamespace.SERVER)) + .isNull(); + }); + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(HealthEndpointProperties.class) static class AutoConfiguredHealthEndpointGroupsTestConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java index 6edda91c3d0a..d1144387ac2a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,9 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration.HealthEndpointGroupMembershipValidator.NoSuchHealthContributorException; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointReactiveWebExtensionConfiguration.WebFluxAdditionalHealthEndpointPathsConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointWebExtensionConfiguration.JerseyAdditionalHealthEndpointPathsConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointWebExtensionConfiguration.MvcAdditionalHealthEndpointPathsConfiguration; import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; @@ -50,12 +53,16 @@ import org.springframework.boot.actuate.health.StatusAggregator; import org.springframework.boot.actuate.health.SystemHealth; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; +import org.springframework.boot.logging.LogLevel; 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.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.DispatcherServlet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -337,6 +344,47 @@ void runWithReactiveContextAndIndicatorsInParentContextFindsIndicators() { })); } + @Test + void additionalHealthEndpointsPathsTolerateHealthEndpointThatIsNotWebExposed() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(DispatcherServletAutoConfiguration.class, + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class)) + .withPropertyValues("management.endpoints.web.exposure.exclude=*", + "management.endpoints.cloudfoundry.exposure.include=*", "spring.main.cloud-platform=cloud_foundry") + .run((context) -> { + assertThat(context).hasSingleBean(MvcAdditionalHealthEndpointPathsConfiguration.class); + assertThat(context).hasNotFailed(); + }); + } + + @Test + void additionalJerseyHealthEndpointsPathsTolerateHealthEndpointThatIsNotWebExposed() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(DispatcherServlet.class)) + .withPropertyValues("management.endpoints.web.exposure.exclude=*", + "management.endpoints.cloudfoundry.exposure.include=*", "spring.main.cloud-platform=cloud_foundry") + .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) + .run((context) -> { + assertThat(context).hasSingleBean(JerseyAdditionalHealthEndpointPathsConfiguration.class); + assertThat(context).hasNotFailed(); + }); + } + + @Test + void additionalReactiveHealthEndpointsPathsTolerateHealthEndpointThatIsNotWebExposed() { + this.reactiveContextRunner + .withConfiguration( + AutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class)) + .withPropertyValues("management.endpoints.web.exposure.exclude=*", + "management.endpoints.cloudfoundry.exposure.include=*", "spring.main.cloud-platform=cloud_foundry") + .run((context) -> { + assertThat(context).hasSingleBean(WebFluxAdditionalHealthEndpointPathsConfiguration.class); + assertThat(context).hasNotFailed(); + }); + } + @Configuration(proxyBeanMethods = false) static class HealthIndicatorsConfiguration { 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 cc2bc1f920b5..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthContributorAutoConfigurationTests.java +++ /dev/null @@ -1,56 +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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.2.0", forRemoval = true) -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..dc276ab5c617 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. @@ -16,11 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.info; +import java.time.Duration; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.ssl.SslHealthIndicatorProperties; import org.springframework.boot.actuate.info.BuildInfoContributor; import org.springframework.boot.actuate.info.EnvironmentInfoContributor; import org.springframework.boot.actuate.info.GitInfoContributor; @@ -28,11 +30,17 @@ 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.actuate.info.SslInfoContributor; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; 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.info.SslInfo; +import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -58,7 +66,8 @@ void envContributor() { @Test void defaultInfoContributorsEnabled() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(InfoContributor.class)); + this.contextRunner.run( + (context) -> assertThat(context).doesNotHaveBean(InfoContributor.class).doesNotHaveBean(SslInfo.class)); } @Test @@ -164,6 +173,64 @@ 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); + }); + } + + @Test + void sslInfoContributor() { + this.contextRunner.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class)) + .withPropertyValues("management.info.ssl.enabled=true", "server.ssl.bundle=ssltest", + "spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks") + .run((context) -> { + assertThat(context).hasSingleBean(SslInfoContributor.class); + assertThat(context).hasSingleBean(SslInfo.class); + Map content = invokeContributor(context.getBean(SslInfoContributor.class)); + assertThat(content).containsKey("ssl"); + assertThat(content.get("ssl")).isInstanceOf(SslInfo.class); + }); + } + + @Test + void sslInfoContributorWithWarningThreshold() { + this.contextRunner.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class)) + .withPropertyValues("management.info.ssl.enabled=true", "server.ssl.bundle=ssltest", + "spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks", + "management.health.ssl.certificate-validity-warning-threshold=1d") + .run((context) -> { + assertThat(context).hasSingleBean(SslInfoContributor.class); + assertThat(context).hasSingleBean(SslInfo.class); + assertThat(context).hasSingleBean(SslHealthIndicatorProperties.class); + assertThat(context.getBean(SslHealthIndicatorProperties.class).getCertificateValidityWarningThreshold()) + .isEqualTo(Duration.ofDays(1)); + Map content = invokeContributor(context.getBean(SslInfoContributor.class)); + assertThat(content).containsKey("ssl"); + assertThat(content.get("ssl")).isInstanceOf(SslInfo.class); + }); + } + + @Test + void customSslInfo() { + this.contextRunner.withUserConfiguration(CustomSslInfoConfiguration.class) + .withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class)) + .withPropertyValues("management.info.ssl.enabled=true", "server.ssl.bundle=ssltest", + "spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks") + .run((context) -> { + assertThat(context).hasSingleBean(SslInfoContributor.class); + assertThat(context).hasSingleBean(SslInfo.class); + assertThat(context.getBean(SslInfo.class)).isSameAs(context.getBean("customSslInfo")); + Map content = invokeContributor(context.getBean(SslInfoContributor.class)); + assertThat(content).containsKey("ssl"); + assertThat(content.get("ssl")).isInstanceOf(SslInfo.class); + }); + } + private Map invokeContributor(InfoContributor contributor) { Info.Builder builder = new Info.Builder(); contributor.contribute(builder); @@ -229,4 +296,14 @@ BuildInfoContributor customBuildInfoContributor() { } + @Configuration(proxyBeanMethods = false) + static class CustomSslInfoConfiguration { + + @Bean + SslInfo customSslInfo(SslBundles sslBundles) { + return new SslInfo(sslBundles, Duration.ofDays(7)); + } + + } + } 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..f09b38cf65e7 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. @@ -25,8 +25,6 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -37,23 +35,22 @@ 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 - * endpoints}. + * Integration tests for the Actuator's MVC + * {@link org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint + * controller endpoints}. * * @author Phillip Webb * @author Andy Wilkinson @@ -69,16 +66,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 +83,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, @@ -120,7 +118,8 @@ static class SecureConfiguration { } - @RestControllerEndpoint(id = "example") + @org.springframework.boot.actuate.endpoint.web.annotation.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/JerseyEndpointAccessIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointAccessIntegrationTests.java new file mode 100644 index 000000000000..79491a393284 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointAccessIntegrationTests.java @@ -0,0 +1,194 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.integrationtest; + +import java.io.IOException; +import java.time.Duration; +import java.util.function.Supplier; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.servlet.DispatcherServlet; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for controlling access to endpoints exposed by Jersey. + * + * @author Andy Wilkinson + */ +class JerseyEndpointAccessIntegrationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, JerseyAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class, + WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, + BeansEndpointAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(DispatcherServlet.class)) + .withUserConfiguration(CustomServletEndpoint.class) + .withPropertyValues("server.port:0"); + + @Test + void accessIsUnrestrictedByDefault() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*").run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isTrue(); + }); + } + + @Test + void accessCanBeReadOnlyByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessCanBeNoneByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=NONE") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessForOneEndpointCanOverrideTheDefaultAccess() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=NONE", "management.endpoint.customservlet.access=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessCanBeCappedAtReadOnly() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", + "management.endpoints.access.max-permitted=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessCanBeCappedAtNone() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", "management.endpoints.access.max-permitted=NONE") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + private WebTestClient createClient(AssertableWebApplicationContext context) { + int port = context.getSourceApplicationContext(ServletWebServerApplicationContext.class) + .getWebServer() + .getPort(); + ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() + .codecs((configurer) -> configurer.defaultCodecs().maxInMemorySize(-1)) + .build(); + return WebTestClient.bindToServer() + .baseUrl("http://localhost:" + port) + .exchangeStrategies(exchangeStrategies) + .responseTimeout(Duration.ofMinutes(5)) + .build(); + } + + private boolean isAccessible(WebTestClient client, HttpMethod method, String path) { + path = "/actuator/" + path; + EntityExchangeResult result = client.method(method).uri(path).exchange().expectBody().returnResult(); + if (result.getStatus() == HttpStatus.OK) { + return true; + } + if (result.getStatus() == HttpStatus.NOT_FOUND || result.getStatus() == HttpStatus.METHOD_NOT_ALLOWED) { + return false; + } + throw new IllegalStateException( + String.format("Unexpected %s HTTP status for endpoint %s", result.getStatus(), path)); + } + + @org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "customservlet") + @SuppressWarnings({ "deprecation", "removal" }) + static class CustomServletEndpoint + implements Supplier { + + @Override + public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() { + return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + } + + }); + } + + } + +} 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..5a3a19644120 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. @@ -30,8 +30,6 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration; @@ -160,12 +158,14 @@ private Class[] getAutoconfigurations(Class... additional) { return autoconfigurations.toArray(new Class[0]); } - @ControllerEndpoint(id = "controller") + @org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint(id = "controller") + @SuppressWarnings("removal") static class TestControllerEndpoint { } - @RestControllerEndpoint(id = "restcontroller") + @org.springframework.boot.actuate.endpoint.web.annotation.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/JmxEndpointAccessIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointAccessIntegrationTests.java new file mode 100644 index 000000000000..b1b9d61e0694 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointAccessIntegrationTests.java @@ -0,0 +1,187 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.integrationtest; + +import javax.management.MBeanOperationInfo; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesAutoConfiguration; +import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; +import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for controlling access to endpoints exposed by JMX. + * + * @author Andy Wilkinson + */ +class JmxEndpointAccessIntegrationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, + JmxEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HttpExchangesAutoConfiguration.class)) + .withUserConfiguration(CustomJmxEndpoint.class) + .withPropertyValues("spring.jmx.enabled=true") + .withConfiguration(AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)); + + @Test + void accessIsUnrestrictedByDefault() { + this.contextRunner.withPropertyValues("management.endpoints.jmx.exposure.include=*").run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + assertThat(hasOperation(mBeanServer, "beans", "beans")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "read")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "write")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "delete")).isTrue(); + }); + } + + @Test + void accessCanBeReadOnlyByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.jmx.exposure.include=*", + "management.endpoints.access.default=READ_ONLY") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + assertThat(hasOperation(mBeanServer, "beans", "beans")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "read")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "write")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "delete")).isFalse(); + }); + } + + @Test + void accessCanBeNoneByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.jmx.exposure.include=*", + "management.endpoints.access.default=NONE") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + assertThat(hasOperation(mBeanServer, "beans", "beans")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "read")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "write")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "delete")).isFalse(); + }); + } + + @Test + void accessForOneEndpointCanOverrideTheDefaultAccess() { + this.contextRunner + .withPropertyValues("management.endpoints.jmx.exposure.include=*", + "management.endpoints.access.default=NONE", "management.endpoint.customjmx.access=UNRESTRICTED") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + assertThat(hasOperation(mBeanServer, "beans", "beans")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "read")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "write")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "delete")).isTrue(); + }); + } + + @Test + void accessCanBeCappedAtReadOnly() { + this.contextRunner + .withPropertyValues("management.endpoints.jmx.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", + "management.endpoints.access.max-permitted=READ_ONLY") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + assertThat(hasOperation(mBeanServer, "beans", "beans")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "read")).isTrue(); + assertThat(hasOperation(mBeanServer, "customjmx", "write")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "delete")).isFalse(); + }); + } + + @Test + void accessCanBeCappedAtNone() { + this.contextRunner.withPropertyValues("management.endpoints.jmx.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", "management.endpoints.access.max-permitted=NONE") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + assertThat(hasOperation(mBeanServer, "beans", "beans")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "read")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "write")).isFalse(); + assertThat(hasOperation(mBeanServer, "customjmx", "delete")).isFalse(); + }); + } + + private ObjectName getDefaultObjectName(String endpointId) { + return getObjectName("org.springframework.boot", endpointId); + } + + private ObjectName getObjectName(String domain, String endpointId) { + try { + return new ObjectName( + String.format("%s:type=Endpoint,name=%s", domain, StringUtils.capitalize(endpointId))); + } + catch (MalformedObjectNameException ex) { + throw new IllegalStateException("Invalid object name", ex); + } + + } + + private boolean hasOperation(MBeanServer mbeanServer, String endpoint, String operationName) { + try { + for (MBeanOperationInfo operation : mbeanServer.getMBeanInfo(getDefaultObjectName(endpoint)) + .getOperations()) { + if (operation.getName().equals(operationName)) { + return true; + } + } + } + catch (Exception ex) { + // Continue + } + return false; + } + + @JmxEndpoint(id = "customjmx") + static class CustomJmxEndpoint { + + @ReadOperation + String read() { + return "read"; + } + + @WriteOperation + String write() { + return "write"; + } + + @DeleteOperation + String delete() { + return "delete"; + } + + } + +} 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 c131bd263a4e..babe9873ae23 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.springframework.boot.SpringBootConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -85,7 +85,7 @@ private ReactiveWebApplicationContextRunner reactiveWebRunner() { RepositoryRestMvcAutoConfiguration.class, HazelcastAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class, BraveAutoConfiguration.class, - OpenTelemetryAutoConfiguration.class }) + OpenTelemetryTracingAutoConfiguration.class }) @SpringBootConfiguration static class WebEndpointTestApplication { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointAccessIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointAccessIntegrationTests.java new file mode 100644 index 000000000000..341cd7c5193c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointAccessIntegrationTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.integrationtest; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; +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.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.reactive.function.client.ExchangeStrategies; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for controlling access to endpoints exposed by Spring WebFlux. + * + * @author Andy Wilkinson + */ +class WebFluxEndpointAccessIntegrationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner( + AnnotationConfigReactiveWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class, + HttpHandlerAutoConfiguration.class, JacksonAutoConfiguration.class, CodecsAutoConfiguration.class, + WebFluxAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + ManagementContextAutoConfiguration.class, ReactiveManagementContextAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) + .withUserConfiguration(CustomWebFluxEndpoint.class) + .withPropertyValues("server.port:0"); + + @Test + void accessIsUnrestrictedByDefault() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*").run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customwebflux")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customwebflux")).isTrue(); + }); + } + + @Test + void accessCanBeReadOnlyByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customwebflux")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customwebflux")).isFalse(); + }); + } + + @Test + void accessCanBeNoneByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=NONE") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customwebflux")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "customwebflux")).isFalse(); + }); + } + + @Test + void accessForOneEndpointCanOverrideTheDefaultAccess() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=NONE", "management.endpoint.customwebflux.access=UNRESTRICTED") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customwebflux")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customwebflux")).isTrue(); + }); + } + + @Test + void accessCanBeCappedAtReadOnly() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", + "management.endpoints.access.max-permitted=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customwebflux")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customwebflux")).isFalse(); + }); + } + + @Test + void accessCanBeCappedAtNone() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", "management.endpoints.access.max-permitted=NONE") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customwebflux")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "customwebflux")).isFalse(); + }); + } + + private WebTestClient createClient(AssertableReactiveWebApplicationContext context) { + int port = context.getSourceApplicationContext(ReactiveWebServerApplicationContext.class) + .getWebServer() + .getPort(); + ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() + .codecs((configurer) -> configurer.defaultCodecs().maxInMemorySize(-1)) + .build(); + return WebTestClient.bindToServer() + .baseUrl("http://localhost:" + port) + .exchangeStrategies(exchangeStrategies) + .responseTimeout(Duration.ofMinutes(5)) + .build(); + } + + private boolean isAccessible(WebTestClient client, HttpMethod method, String path) { + path = "/actuator/" + path; + EntityExchangeResult result = client.method(method).uri(path).exchange().expectBody().returnResult(); + if (result.getStatus() == HttpStatus.OK) { + return true; + } + if (result.getStatus() == HttpStatus.NOT_FOUND || result.getStatus() == HttpStatus.METHOD_NOT_ALLOWED) { + return false; + } + throw new IllegalStateException( + String.format("Unexpected %s HTTP status for endpoint %s", result.getStatus(), path)); + } + + @org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "customwebflux") + @SuppressWarnings("removal") + static class CustomWebFluxEndpoint { + + @GetMapping("/") + String get() { + return "get"; + } + + @PostMapping("/") + String post() { + return "post"; + } + + } + +} 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..ab3fc50a853c 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. @@ -25,8 +25,6 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -107,12 +105,14 @@ private WebTestClient createWebTestClient(ApplicationContext context) { .build(); } - @ControllerEndpoint(id = "controller") + @org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint(id = "controller") + @SuppressWarnings("removal") static class TestControllerEndpoint { } - @RestControllerEndpoint(id = "restcontroller") + @org.springframework.boot.actuate.endpoint.web.annotation.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/WebMvcEndpointAccessIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointAccessIntegrationTests.java new file mode 100644 index 000000000000..b574f05fc280 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointAccessIntegrationTests.java @@ -0,0 +1,228 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.integrationtest; + +import java.io.IOException; +import java.time.Duration; +import java.util.function.Supplier; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +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.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.reactive.function.client.ExchangeStrategies; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for controlling access to endpoints exposed by Spring MVC. + * + * @author Andy Wilkinson + */ +class WebMvcEndpointAccessIntegrationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(ServletWebServerFactoryAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, + HealthContributorAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) + .withUserConfiguration(CustomMvcEndpoint.class, CustomServletEndpoint.class) + .withPropertyValues("server.port:0"); + + @Test + void accessIsUnrestrictedByDefault() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*").run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isTrue(); + }); + } + + @Test + void accessCanBeReadOnlyByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessCanBeNoneByDefault() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=NONE") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessForOneEndpointCanOverrideTheDefaultAccess() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=READ_ONLY", + "management.endpoint.customservlet.access=UNRESTRICTED") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isTrue(); + }); + } + + @Test + void accessCanBeCappedAtReadOnly() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", + "management.endpoints.access.max-permitted=READ_ONLY") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + @Test + void accessCanBeCappedAtNone() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.access.default=UNRESTRICTED", "management.endpoints.access.max-permitted=NONE") + .run((context) -> { + WebTestClient client = createClient(context); + assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse(); + assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isFalse(); + assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse(); + }); + } + + private WebTestClient createClient(AssertableWebApplicationContext context) { + int port = context.getSourceApplicationContext(ServletWebServerApplicationContext.class) + .getWebServer() + .getPort(); + ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() + .codecs((configurer) -> configurer.defaultCodecs().maxInMemorySize(-1)) + .build(); + return WebTestClient.bindToServer() + .baseUrl("http://localhost:" + port) + .exchangeStrategies(exchangeStrategies) + .responseTimeout(Duration.ofMinutes(5)) + .build(); + } + + private boolean isAccessible(WebTestClient client, HttpMethod method, String path) { + path = "/actuator/" + path; + EntityExchangeResult result = client.method(method).uri(path).exchange().expectBody().returnResult(); + if (result.getStatus() == HttpStatus.OK) { + return true; + } + if (result.getStatus() == HttpStatus.NOT_FOUND || result.getStatus() == HttpStatus.METHOD_NOT_ALLOWED) { + return false; + } + throw new IllegalStateException( + String.format("Unexpected %s HTTP status for endpoint %s", result.getStatus(), path)); + } + + @org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "custommvc") + @SuppressWarnings("removal") + static class CustomMvcEndpoint { + + @GetMapping("/") + String get() { + return "get"; + } + + @PostMapping("/") + String post() { + return "post"; + } + + } + + @org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "customservlet") + @SuppressWarnings({ "deprecation", "removal" }) + static class CustomServletEndpoint + implements Supplier { + + @Override + public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() { + return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + } + + }); + } + + } + +} 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..553a5ad92c65 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. @@ -33,9 +33,6 @@ import org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.web.EndpointServlet; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -189,7 +186,8 @@ private boolean isExposed(WebTestClient client, HttpMethod method, String path) String.format("Unexpected %s HTTP status for endpoint %s", result.getStatus(), path)); } - @RestControllerEndpoint(id = "custommvc") + @org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "custommvc") + @SuppressWarnings("removal") static class CustomMvcEndpoint { @GetMapping("/") @@ -199,12 +197,14 @@ String main() { } - @ServletEndpoint(id = "customservlet") - static class CustomServletEndpoint implements Supplier { + @org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "customservlet") + @SuppressWarnings({ "deprecation", "removal" }) + static class CustomServletEndpoint + implements Supplier { @Override - public EndpointServlet get() { - return new EndpointServlet(new HttpServlet() { + public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() { + return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) 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..ccc8acb95871 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. @@ -28,10 +28,6 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.web.EndpointServlet; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -47,24 +43,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 +81,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 +108,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, @@ -197,23 +186,27 @@ static class SecureConfiguration { } - @ServletEndpoint(id = "servlet") - static class TestServletEndpoint implements Supplier { + @org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "servlet") + @SuppressWarnings({ "deprecation", "removal" }) + static class TestServletEndpoint + implements Supplier { @Override - public EndpointServlet get() { - return new EndpointServlet(new HttpServlet() { + public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() { + return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() { }); } } - @ControllerEndpoint(id = "controller") + @org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint(id = "controller") + @SuppressWarnings("removal") static class TestControllerEndpoint { } - @RestControllerEndpoint(id = "restcontroller") + @org.springframework.boot.actuate.endpoint.web.annotation.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/OnEnabledLoggingExportConditionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java new file mode 100644 index 000000000000..1bca2e10715a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OnEnabledLoggingExportConditionTests.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +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 OnEnabledLoggingExportCondition}. + * + * @author Moritz Halbritter + * @author Dmytro Nosan + */ +class OnEnabledLoggingExportConditionTests { + + @Test + void shouldMatchIfNoPropertyIsSet() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(), mockMetadata("")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()).isEqualTo("@ConditionalOnEnabledLoggingExport is enabled by default"); + } + + @Test + void shouldNotMatchIfGlobalPropertyIsFalse() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome( + mockConditionContext(Map.of("management.logging.export.enabled", "false")), mockMetadata("")); + assertThat(outcome.isMatch()).isFalse(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledLoggingExport management.logging.export.enabled is false"); + } + + @Test + void shouldMatchIfGlobalPropertyIsTrue() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome( + mockConditionContext(Map.of("management.logging.export.enabled", "true")), mockMetadata("")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledLoggingExport management.logging.export.enabled is true"); + } + + @Test + void shouldNotMatchIfExporterPropertyIsFalse() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome( + mockConditionContext(Map.of("management.otlp.logging.export.enabled", "false")), mockMetadata("otlp")); + assertThat(outcome.isMatch()).isFalse(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is false"); + } + + @Test + void shouldMatchIfExporterPropertyIsTrue() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome( + mockConditionContext(Map.of("management.otlp.logging.export.enabled", "true")), mockMetadata("otlp")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is true"); + } + + @Test + void exporterPropertyShouldOverrideGlobalPropertyIfTrue() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext( + Map.of("management.logging.enabled", "false", "management.otlp.logging.export.enabled", "true")), + mockMetadata("otlp")); + assertThat(outcome.isMatch()).isTrue(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is true"); + } + + @Test + void exporterPropertyShouldOverrideGlobalPropertyIfFalse() { + OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition(); + ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext( + Map.of("management.logging.enabled", "true", "management.otlp.logging.export.enabled", "false")), + mockMetadata("otlp")); + assertThat(outcome.isMatch()).isFalse(); + assertThat(outcome.getMessage()) + .isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.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(ConditionalOnEnabledLoggingExport.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/logging/OpenTelemetryLoggingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/OpenTelemetryLoggingAutoConfigurationTests.java new file mode 100644 index 000000000000..ce9b89aa0083 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/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; + +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.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; +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(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 shouldAllowMultipleLogRecordExporters() { + this.contextRunner.withUserConfiguration(MultipleLogRecordExportersConfig.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 shouldAllowMultipleLogRecordProcessorsInAdditionToBatchLogRecordProcessor() { + this.contextRunner.withUserConfiguration(MultipleLogRecordProcessorsConfig.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 shouldAllowMultipleSdkLoggerProviderBuilderCustomizers() { + this.contextRunner.withUserConfiguration(MultipleSdkLoggerProviderBuilderCustomizersConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(SdkLoggerProvider.class); + assertThat(context.getBeansOfType(SdkLoggerProviderBuilderCustomizer.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 MultipleLogRecordExportersConfig { + + @Bean + public LogRecordExporter customLogRecordExporter1() { + return new NoopLogRecordExporter(); + } + + @Bean + public LogRecordExporter customLogRecordExporter2() { + return new NoopLogRecordExporter(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class MultipleLogRecordProcessorsConfig { + + @Bean + public LogRecordProcessor customLogRecordProcessor1() { + return new NoopLogRecordProcessor(); + } + + @Bean + public LogRecordProcessor customLogRecordProcessor2() { + return new NoopLogRecordProcessor(); + } + + } + + @Configuration(proxyBeanMethods = false) + public static class MultipleSdkLoggerProviderBuilderCustomizersConfig { + + @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/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..dce475ccb4ea --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/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.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.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 + */ +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/otlp/OtlpLoggingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java new file mode 100644 index 000000000000..34a70bca0d26 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java @@ -0,0 +1,188 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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.getUrl(Transport.HTTP)).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 shouldBackOffWhenLoggingExportPropertyIsNotEnabled() { + this.contextRunner + .withPropertyValues("management.logging.export.enabled=false", + "management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(LogRecordExporter.class); + }); + } + + @Test + void shouldBackOffWhenOtlpLoggingExportPropertyIsNotEnabled() { + this.contextRunner + .withPropertyValues("management.otlp.logging.export.enabled=false", + "management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class); + assertThat(context).doesNotHaveBean(LogRecordExporter.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 shouldBackOffWhenCustomOtlpLoggingConnectionDetailsIsDefined() { + this.contextRunner.withUserConfiguration(CustomOtlpLoggingConnectionDetails.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")); + }); + + } + + @Test + void shouldUseHttpExporterIfTransportIsNotSet() { + this.contextRunner.withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) + .hasSingleBean(LogRecordExporter.class); + assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class); + }); + } + + @Test + void shouldUseHttpExporterIfTransportIsSetToHttp() { + this.contextRunner + .withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", + "management.otlp.logging.transport=http") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpHttpLogRecordExporter.class) + .hasSingleBean(LogRecordExporter.class); + assertThat(context).doesNotHaveBean(OtlpGrpcLogRecordExporter.class); + }); + } + + @Test + void shouldUseGrpcExporterIfTransportIsSetToGrpc() { + this.contextRunner + .withPropertyValues("management.otlp.logging.endpoint=http://localhost:4318/v1/logs", + "management.otlp.logging.transport=grpc") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpGrpcLogRecordExporter.class) + .hasSingleBean(LogRecordExporter.class); + assertThat(context).doesNotHaveBean(OtlpHttpLogRecordExporter.class); + }); + } + + @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 CustomOtlpLoggingConnectionDetails { + + @Bean + public OtlpLoggingConnectionDetails customOtlpLoggingConnectionDetails() { + return (transport) -> "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/cache/CacheMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.java index 1d3894f07ff6..9cd4365df026 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 +51,9 @@ void autoConfiguredCache2kIsInstrumented() { @Test void autoConfiguredCacheManagerIsInstrumented() { - this.contextRunner.withPropertyValues("spring.cache.type=caffeine", "spring.cache.cache-names=cache1,cache2") + this.contextRunner + .withPropertyValues("spring.cache.type=caffeine", "spring.cache.cache-names=cache1,cache2", + "spring.cache.caffeine.spec=recordStats") .run((context) -> { MeterRegistry registry = context.getBean(MeterRegistry.class); registry.get("cache.gets").tags("name", "cache1").tags("cache.manager", "cacheManager").meter(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapterTests.java index 0738af128059..f27e4a461b54 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/elastic/ElasticPropertiesConfigAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,4 +104,11 @@ void whenPropertiesApiKeyCredentialsIsSetAdapterPipelineReturnsIt() { assertThat(new ElasticPropertiesConfigAdapter(properties).apiKeyCredentials()).isEqualTo("secret"); } + @Test + void whenPropertiesEnableSourceIsSetAdapterEnableSourceReturnsIt() { + ElasticProperties properties = new ElasticProperties(); + properties.setEnableSource(true); + assertThat(new ElasticPropertiesConfigAdapter(properties).enableSource()).isTrue(); + } + } 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 f303e4a2cfdc..64a9d65aa727 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,19 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; +import java.util.concurrent.ScheduledExecutorService; + import io.micrometer.core.instrument.Clock; import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; 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.boot.testsupport.assertj.ScheduledExecutorServiceAssert; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -76,6 +81,32 @@ void allowsCustomConfigToBeUsed() { .hasBean("customConfig")); } + @Test + void allowsPlatformThreadsToBeUsed() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("scheduledExecutorService") + .satisfies((executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor) + .usesPlatformThreads()); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void allowsVirtualThreadsToBeUsed() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("scheduledExecutorService") + .satisfies( + (executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor) + .usesVirtualThreads()); + }); + } + @Test void allowsRegistryToBeCustomized() { this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java new file mode 100644 index 000000000000..ba3276ac927c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapterTests.java @@ -0,0 +1,207 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.micrometer.registry.otlp.AggregationTemporality; +import io.micrometer.registry.otlp.HistogramFlavor; +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 OtlpMetricsPropertiesConfigAdapter}. + * + * @author Eddú Meléndez + * @author Moritz Halbritter + */ +class OtlpMetricsPropertiesConfigAdapterTests { + + private OtlpMetricsProperties properties; + + private OpenTelemetryProperties openTelemetryProperties; + + private MockEnvironment environment; + + private OtlpMetricsConnectionDetails connectionDetails; + + @BeforeEach + void setUp() { + this.properties = new OtlpMetricsProperties(); + this.openTelemetryProperties = new OpenTelemetryProperties(); + this.environment = new MockEnvironment(); + this.connectionDetails = new PropertiesOtlpMetricsConnectionDetails(this.properties); + } + + @Test + void whenPropertiesUrlIsSetAdapterUrlReturnsIt() { + this.properties.setUrl("http://another-url:4318/v1/metrics"); + assertThat(createAdapter().url()).isEqualTo("http://another-url:4318/v1/metrics"); + } + + @Test + void whenPropertiesAggregationTemporalityIsNotSetAdapterAggregationTemporalityReturnsCumulative() { + assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.CUMULATIVE); + } + + @Test + void whenPropertiesAggregationTemporalityIsSetAdapterAggregationTemporalityReturnsIt() { + this.properties.setAggregationTemporality(AggregationTemporality.DELTA); + assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.DELTA); + } + + @Test + @SuppressWarnings("removal") + void whenPropertiesResourceAttributesIsSetAdapterResourceAttributesReturnsIt() { + this.properties.setResourceAttributes(Map.of("service.name", "boot-service")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "boot-service"); + } + + @Test + void whenPropertiesHeadersIsSetAdapterHeadersReturnsIt() { + this.properties.setHeaders(Map.of("header", "value")); + assertThat(createAdapter().headers()).containsEntry("header", "value"); + } + + @Test + void whenPropertiesHistogramFlavorIsNotSetAdapterHistogramFlavorReturnsExplicitBucketHistogram() { + assertThat(createAdapter().histogramFlavor()).isSameAs(HistogramFlavor.EXPLICIT_BUCKET_HISTOGRAM); + } + + @Test + void whenPropertiesHistogramFlavorIsSetAdapterHistogramFlavorReturnsIt() { + this.properties.setHistogramFlavor(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM); + assertThat(createAdapter().histogramFlavor()).isSameAs(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM); + } + + @Test + void whenPropertiesMaxScaleIsNotSetAdapterMaxScaleReturns20() { + assertThat(createAdapter().maxScale()).isEqualTo(20); + } + + @Test + void whenPropertiesMaxScaleIsSetAdapterMaxScaleReturnsIt() { + this.properties.setMaxScale(5); + assertThat(createAdapter().maxScale()).isEqualTo(5); + } + + @Test + void whenPropertiesMaxBucketCountIsNotSetAdapterMaxBucketCountReturns160() { + assertThat(createAdapter().maxBucketCount()).isEqualTo(160); + } + + @Test + void whenPropertiesMaxBucketCountIsSetAdapterMaxBucketCountReturnsIt() { + this.properties.setMaxBucketCount(6); + assertThat(createAdapter().maxBucketCount()).isEqualTo(6); + } + + @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 OtlpMetricsPropertiesConfigAdapter createAdapter() { + return new OtlpMetricsPropertiesConfigAdapter(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/OtlpMetricsPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesTests.java new file mode 100644 index 000000000000..36f2f85af6ec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesTests.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.actuate.autoconfigure.metrics.export.otlp; + +import io.micrometer.registry.otlp.OtlpConfig; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesTests; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OtlpMetricsProperties}. + * + * @author Eddú Meléndez + */ +class OtlpMetricsPropertiesTests extends StepRegistryPropertiesTests { + + @Test + void defaultValuesAreConsistent() { + OtlpMetricsProperties properties = new OtlpMetricsProperties(); + OtlpConfig config = OtlpConfig.DEFAULT; + assertStepRegistryDefaultValues(properties, config); + assertThat(properties.getUrl()).isEqualTo(config.url()); + assertThat(properties.getAggregationTemporality()).isSameAs(config.aggregationTemporality()); + assertThat(properties.getHistogramFlavor()).isSameAs(config.histogramFlavor()); + assertThat(properties.getMaxScale()).isEqualTo(config.maxScale()); + assertThat(properties.getMaxBucketCount()).isEqualTo(config.maxBucketCount()); + 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/otlp/OtlpPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java deleted file mode 100644 index 25151b63a64c..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java +++ /dev/null @@ -1,147 +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.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() { - this.properties.setUrl("http://another-url:4318/v1/metrics"); - assertThat(createAdapter().url()).isEqualTo("http://another-url:4318/v1/metrics"); - } - - @Test - void whenPropertiesAggregationTemporalityIsNotSetAdapterAggregationTemporalityReturnsCumulative() { - assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.CUMULATIVE); - } - - @Test - void whenPropertiesAggregationTemporalityIsSetAdapterAggregationTemporalityReturnsIt() { - this.properties.setAggregationTemporality(AggregationTemporality.DELTA); - assertThat(createAdapter().aggregationTemporality()).isSameAs(AggregationTemporality.DELTA); - } - - @Test - @SuppressWarnings("removal") - void whenPropertiesResourceAttributesIsSetAdapterResourceAttributesReturnsIt() { - this.properties.setResourceAttributes(Map.of("service.name", "boot-service")); - assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "boot-service"); - } - - @Test - void whenPropertiesHeadersIsSetAdapterHeadersReturnsIt() { - 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", "application"); - } - - 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 deleted file mode 100644 index 3046e2279dca..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java +++ /dev/null @@ -1,43 +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.metrics.export.otlp; - -import io.micrometer.registry.otlp.OtlpConfig; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesTests; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OtlpProperties}. - * - * @author Eddú Meléndez - */ -class OtlpPropertiesTests extends StepRegistryPropertiesTests { - - @Test - void defaultValuesAreConsistent() { - OtlpProperties properties = new OtlpProperties(); - OtlpConfig config = OtlpConfig.DEFAULT; - 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/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/observation/ObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java index 655d2eb0fbf1..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 @@ -50,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; @@ -334,6 +335,46 @@ void shouldDisableSpringSecurityObservationsIfPropertyIsSet() { }); } + @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 { 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 3fd1a2b61bef..f9ba22dedae7 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. @@ -40,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}. @@ -169,18 +166,17 @@ void shouldNotDenyNorLogIfMaxUrisIsNotReached(CapturedOutput output) { }); } - private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context) throws Exception { + private MeterRegistry getInitializedMeterRegistry(AssertableWebApplicationContext context) { 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); } 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 index 791e326a951b..14ab7b3c6380 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,6 @@ 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; @@ -86,7 +85,24 @@ 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")); + .contains(entry(AttributeKey.stringKey("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")); }); } @@ -95,7 +111,7 @@ void shouldFallbackToDefaultApplicationNameIfSpringApplicationNameIsNotSet() { this.runner.run((context) -> { Resource resource = context.getBean(Resource.class); assertThat(resource.getAttributes().asMap()) - .contains(entry(ResourceAttributes.SERVICE_NAME, "application")); + .contains(entry(AttributeKey.stringKey("service.name"), "unknown_service")); }); } 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 index e5ae366b49bd..7f827a6343dc 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 +30,11 @@ 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.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -43,11 +44,13 @@ * Tests for {@link R2dbcObservationAutoConfiguration}. * * @author Moritz Halbritter + * @author Tadaya Tsuyukubo */ class R2dbcObservationAutoConfigurationTests { private final ApplicationContextRunner runnerWithoutObservationRegistry = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(R2dbcObservationAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(R2dbcProxyAutoConfiguration.class, R2dbcObservationAutoConfiguration.class)); private final ApplicationContextRunner runner = this.runnerWithoutObservationRegistry .withBean(ObservationRegistry.class, ObservationRegistry::create); @@ -58,27 +61,10 @@ void shouldBeRegisteredInAutoConfigurationImports() { .contains(R2dbcObservationAutoConfiguration.class.getName()); } - @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 shouldNotSupplyBeansIfObservationRegistryIsNotPresent() { this.runnerWithoutObservationRegistry - .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); + .run((context) -> assertThat(context).doesNotHaveBean(ProxyConnectionFactoryCustomizer.class)); } @Test 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..5e72c126d0f9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/sbom/SbomEndpointAutoConfigurationTests.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.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 runWhenWebExposedShouldHaveEndpointBeanAndWebExtension() { + this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sbom") + .run((context) -> assertThat(context).hasSingleBean(SbomEndpoint.class) + .hasSingleBean(SbomEndpointWebExtension.class)); + } + + @Test + void runWhenCloudFoundryExposedShouldHaveEndpointBeanAndWebExtension() { + this.contextRunner + .withPropertyValues("management.endpoints.cloud-foundry.exposure.include=sbom", + "spring.main.cloud-platform=cloud_foundry") + .run((context) -> assertThat(context).hasSingleBean(SbomEndpoint.class) + .hasSingleBean(SbomEndpointWebExtension.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/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..792ed54ca9c0 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 @@ -18,6 +18,7 @@ import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.assertj.core.api.AssertDelegateTarget; @@ -30,7 +31,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.context.support.StaticApplicationContext; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -235,6 +236,13 @@ void toStringWhenIncludedExcludedEndpoints() { assertThat(matcher).hasToString("EndpointRequestMatcher includes=[*], excludes=[bar], includeLinks=false"); } + @Test + void toStringWhenToAdditionalPaths() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, "test"); + assertThat(matcher) + .hasToString("AdditionalPathsEndpointServerWebExchangeMatcher endpoints=[test], webServerNamespace=server"); + } + @Test void toAnyEndpointWhenEndpointPathMappedToRootIsExcludedShouldNotMatchRoot() { ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("root"); @@ -253,6 +261,43 @@ void toEndpointWhenEndpointPathMappedToRootShouldMatchRoot() { assertMatcher.matches("/"); } + @Test + void toAdditionalPathsWithEndpointClassShouldMatchAdditionalPath() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + FooEndpoint.class); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.matches("/additional"); + } + + @Test + void toAdditionalPathsWithEndpointIdShouldMatchAdditionalPath() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, "foo"); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.matches("/additional"); + } + + @Test + void toAdditionalPathsWithEndpointClassShouldNotMatchOtherPaths() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + FooEndpoint.class); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.doesNotMatch("/foo"); + assertMatcher.doesNotMatch("/bar"); + } + + @Test + void toAdditionalPathsWithEndpointClassShouldNotMatchOtherNamespace() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + FooEndpoint.class); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional"))), + WebServerNamespace.MANAGEMENT); + assertMatcher.doesNotMatch("/additional"); + } + private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) { return assertMatcher(matcher, mockPathMappedEndpoints("/actuator")); } @@ -261,23 +306,20 @@ private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, Str return assertMatcher(matcher, mockPathMappedEndpoints(basePath)); } - private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { - List> endpoints = new ArrayList<>(); - endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); - endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); - return new PathMappedEndpoints(basePath, () -> endpoints); - } - - private TestEndpoint mockEndpoint(EndpointId id, String rootPath) { - TestEndpoint endpoint = mock(TestEndpoint.class); - given(endpoint.getEndpointId()).willReturn(id); - given(endpoint.getRootPath()).willReturn(rootPath); - return endpoint; + private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, + PathMappedEndpoints pathMappedEndpoints) { + return assertMatcher(matcher, pathMappedEndpoints, null); } private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, - PathMappedEndpoints pathMappedEndpoints) { + PathMappedEndpoints pathMappedEndpoints, WebServerNamespace namespace) { StaticApplicationContext context = new StaticApplicationContext(); + if (namespace != null && !WebServerNamespace.SERVER.equals(namespace)) { + StaticApplicationContext parentContext = new StaticApplicationContext(); + parentContext.setId("app"); + context.setParent(parentContext); + context.setId(parentContext.getId() + ":" + namespace); + } context.registerBean(WebEndpointProperties.class); if (pathMappedEndpoints != null) { context.registerBean(PathMappedEndpoints.class, () -> pathMappedEndpoints); @@ -289,6 +331,26 @@ private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, return assertThat(new RequestMatcherAssert(context, matcher)); } + private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { + List> endpoints = new ArrayList<>(); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); + return new PathMappedEndpoints(basePath, () -> endpoints); + } + + private TestEndpoint mockEndpoint(EndpointId id, String rootPath) { + return mockEndpoint(id, rootPath, WebServerNamespace.SERVER); + } + + private TestEndpoint mockEndpoint(EndpointId id, String rootPath, WebServerNamespace webServerNamespace, + String... additionalPaths) { + TestEndpoint endpoint = mock(TestEndpoint.class); + given(endpoint.getEndpointId()).willReturn(id); + given(endpoint.getRootPath()).willReturn(rootPath); + given(endpoint.getAdditionalPaths(webServerNamespace)).willReturn(Arrays.asList(additionalPaths)); + return endpoint; + } + static class RequestMatcherAssert implements AssertDelegateTarget { private final StaticApplicationContext context; @@ -354,7 +416,8 @@ static class FooEndpoint { } - @ServletEndpoint(id = "baz") + @org.springframework.boot.actuate.endpoint.web.annotation.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 66b39ded5a73..f632721a5608 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 @@ -79,6 +79,35 @@ void permitAllForHealth() { .run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull()); } + @Test + void withAdditionalPathsOnSamePort() { + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + .withPropertyValues("management.endpoint.health.group.test1.include=*", + "management.endpoint.health.group.test2.include=*", + "management.endpoint.health.group.test1.additional-path=server:/check1", + "management.endpoint.health.group.test2.additional-path=management:/check2") + .run((context) -> { + assertThat(getAuthenticateHeader(context, "/check1")).isNull(); + assertThat(getAuthenticateHeader(context, "/check2").get(0)).contains("Basic realm="); + assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull(); + }); + } + + @Test + void withAdditionalPathsOnDifferentPort() { + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + .withPropertyValues("management.endpoint.health.group.test1.include=*", + "management.endpoint.health.group.test2.include=*", + "management.endpoint.health.group.test1.additional-path=server:/check1", + "management.endpoint.health.group.test2.additional-path=management:/check2", + "management.server.port=0") + .run((context) -> { + assertThat(getAuthenticateHeader(context, "/check1")).isNull(); + assertThat(getAuthenticateHeader(context, "/check2").get(0)).contains("Basic realm="); + assertThat(getAuthenticateHeader(context, "/actuator/health").get(0)).contains("Basic realm="); + }); + } + @Test void securesEverythingElse() { this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> { 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 9417437d92f7..6a2ca3dbb028 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. @@ -32,8 +32,6 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.boot.actuate.endpoint.web.EndpointServlet; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; @@ -177,12 +175,14 @@ Object getAll() { } - @ServletEndpoint(id = "se1") - static class TestServletEndpoint implements Supplier { + @org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "se1") + @SuppressWarnings({ "deprecation", "removal" }) + static class TestServletEndpoint + implements Supplier { @Override - public EndpointServlet get() { - return new EndpointServlet(ExampleServlet.class); + public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() { + return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(ExampleServlet.class); } } 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..ac07f1d6ef7c 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 @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.security.servlet; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import jakarta.servlet.http.HttpServletRequest; @@ -24,6 +25,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.AdditionalPathsEndpointRequestMatcher; import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.EndpointRequestMatcher; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; @@ -31,8 +33,10 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; -import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.server.WebServer; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -195,7 +199,7 @@ void endpointRequestMatcherShouldUseCustomRequestMatcherProvider() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); RequestMatcher mockRequestMatcher = (request) -> false; RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), - (pattern) -> mockRequestMatcher); + (pattern) -> mockRequestMatcher, null); assertMatcher.doesNotMatch("/foo"); assertMatcher.doesNotMatch("/bar"); } @@ -205,7 +209,7 @@ void linksRequestMatcherShouldUseCustomRequestMatcherProvider() { RequestMatcher matcher = EndpointRequest.toLinks(); RequestMatcher mockRequestMatcher = (request) -> false; RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints("/actuator"), - (pattern) -> mockRequestMatcher); + (pattern) -> mockRequestMatcher, null); assertMatcher.doesNotMatch("/actuator"); } @@ -240,6 +244,13 @@ void toStringWhenIncludedExcludedEndpoints() { assertThat(matcher).hasToString("EndpointRequestMatcher includes=[*], excludes=[bar], includeLinks=false"); } + @Test + void toStringWhenToAdditionalPaths() { + RequestMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, "test"); + assertThat(matcher) + .hasToString("AdditionalPathsEndpointRequestMatcher endpoints=[test], webServerNamespace=server"); + } + @Test void toAnyEndpointWhenEndpointPathMappedToRootIsExcludedShouldNotMatchRoot() { EndpointRequestMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("root"); @@ -258,12 +269,50 @@ void toEndpointWhenEndpointPathMappedToRootShouldMatchRoot() { assertMatcher.matches("/"); } + @Test + void toAdditionalPathsWithEndpointClassShouldMatchAdditionalPath() { + AdditionalPathsEndpointRequestMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + FooEndpoint.class); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.matches("/additional"); + } + + @Test + void toAdditionalPathsWithEndpointIdShouldMatchAdditionalPath() { + AdditionalPathsEndpointRequestMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + "foo"); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.matches("/additional"); + } + + @Test + void toAdditionalPathsWithEndpointClassShouldNotMatchOtherPaths() { + AdditionalPathsEndpointRequestMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + FooEndpoint.class); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.doesNotMatch("/foo"); + assertMatcher.doesNotMatch("/bar"); + } + + @Test + void toAdditionalPathsWithEndpointClassShouldNotMatchOtherNamespace() { + AdditionalPathsEndpointRequestMatcher matcher = EndpointRequest.toAdditionalPaths(WebServerNamespace.SERVER, + FooEndpoint.class); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", + () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional"))), + null, WebServerNamespace.MANAGEMENT); + assertMatcher.doesNotMatch("/additional"); + } + private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { return assertMatcher(matcher, mockPathMappedEndpoints("/actuator")); } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath) { - return assertMatcher(matcher, mockPathMappedEndpoints(basePath), null); + return assertMatcher(matcher, mockPathMappedEndpoints(basePath), null, null); } private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { @@ -274,19 +323,26 @@ private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { } private TestEndpoint mockEndpoint(EndpointId id, String rootPath) { + return mockEndpoint(id, rootPath, WebServerNamespace.SERVER); + } + + private TestEndpoint mockEndpoint(EndpointId id, String rootPath, WebServerNamespace webServerNamespace, + String... additionalPaths) { TestEndpoint endpoint = mock(TestEndpoint.class); given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getRootPath()).willReturn(rootPath); + given(endpoint.getAdditionalPaths(webServerNamespace)).willReturn(Arrays.asList(additionalPaths)); return endpoint; } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, PathMappedEndpoints pathMappedEndpoints) { - return assertMatcher(matcher, pathMappedEndpoints, null); + return assertMatcher(matcher, pathMappedEndpoints, null, null); } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, PathMappedEndpoints pathMappedEndpoints, - RequestMatcherProvider matcherProvider) { - StaticWebApplicationContext context = new StaticWebApplicationContext(); + RequestMatcherProvider matcherProvider, WebServerNamespace webServerNamespace) { + StaticWebApplicationContext context = (webServerNamespace != null) + ? new NamedStaticWebApplicationContext(webServerNamespace) : new StaticWebApplicationContext(); context.registerBean(WebEndpointProperties.class); if (pathMappedEndpoints != null) { context.registerBean(PathMappedEndpoints.class, () -> pathMappedEndpoints); @@ -301,6 +357,27 @@ private RequestMatcherAssert assertMatcher(RequestMatcher matcher, PathMappedEnd return assertThat(new RequestMatcherAssert(context, matcher)); } + static class NamedStaticWebApplicationContext extends StaticWebApplicationContext + implements WebServerApplicationContext { + + private final WebServerNamespace webServerNamespace; + + NamedStaticWebApplicationContext(WebServerNamespace webServerNamespace) { + this.webServerNamespace = webServerNamespace; + } + + @Override + public WebServer getWebServer() { + return null; + } + + @Override + public String getServerNamespace() { + return this.webServerNamespace.getValue(); + } + + } + static class RequestMatcherAssert implements AssertDelegateTarget { private final WebApplicationContext context; @@ -353,7 +430,8 @@ static class FooEndpoint { } - @ServletEndpoint(id = "baz") + @org.springframework.boot.actuate.endpoint.web.annotation.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/servlet/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java index 869ee26aa4af..b60d93ee2dfa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.IOException; import java.util.List; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -36,6 +37,9 @@ import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -48,6 +52,7 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -63,11 +68,17 @@ class ManagementWebSecurityAutoConfigurationTests { private static final String MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN = "managementSecurityFilterChain"; - private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration( - AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, - EndpointAutoConfiguration.class, WebMvcAutoConfiguration.class, WebEndpointAutoConfiguration.class, - SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class)); + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(contextSupplier(), + WebServerApplicationContext.class) + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, + EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, + WebMvcAutoConfiguration.class, WebEndpointAutoConfiguration.class, SecurityAutoConfiguration.class, + ManagementWebSecurityAutoConfiguration.class)); + + private static Supplier contextSupplier() { + return WebApplicationContextRunner.withMockServletContext(MockWebServerApplicationContext::new); + } @Test void permitAllForHealth() { @@ -159,6 +170,33 @@ void backOffIfRemoteDevToolsSecurityFilterChainIsPresent() { }); } + @Test + void withAdditionalPathsOnSamePort() { + this.contextRunner + .withPropertyValues("management.endpoint.health.group.test1.include=*", + "management.endpoint.health.group.test2.include=*", + "management.endpoint.health.group.test1.additional-path=server:/check1", + "management.endpoint.health.group.test2.additional-path=management:/check2") + .run((context) -> { + assertThat(getResponseStatus(context, "/check1")).isEqualTo(HttpStatus.OK); + assertThat(getResponseStatus(context, "/check2")).isEqualTo(HttpStatus.UNAUTHORIZED); + assertThat(getResponseStatus(context, "/actuator/health")).isEqualTo(HttpStatus.OK); + }); + } + + @Test + void withAdditionalPathsOnDifferentPort() { + this.contextRunner.withPropertyValues("management.endpoint.health.group.test1.include=*", + "management.endpoint.health.group.test2.include=*", + "management.endpoint.health.group.test1.additional-path=server:/check1", + "management.endpoint.health.group.test2.additional-path=management:/check2", "management.server.port=0") + .run((context) -> { + assertThat(getResponseStatus(context, "/check1")).isEqualTo(HttpStatus.OK); + assertThat(getResponseStatus(context, "/check2")).isEqualTo(HttpStatus.UNAUTHORIZED); + assertThat(getResponseStatus(context, "/actuator/health")).isEqualTo(HttpStatus.UNAUTHORIZED); + }); + } + private HttpStatus getResponseStatus(AssertableWebApplicationContext context, String path) throws IOException, jakarta.servlet.ServletException { FilterChainProxy filterChainProxy = context.getBean(FilterChainProxy.class); @@ -214,4 +252,19 @@ SecurityFilterChain testRemoteDevToolsSecurityFilterChain(HttpSecurity http) thr } + static class MockWebServerApplicationContext extends AnnotationConfigServletWebApplicationContext + implements WebServerApplicationContext { + + @Override + public WebServer getWebServer() { + return null; + } + + @Override + public String getServerNamespace() { + return "server"; + } + + } + } 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/ssl/SslHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java new file mode 100644 index 000000000000..d122d5041afc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ssl; + +import java.time.Duration; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.ssl.SslHealthContributorAutoConfigurationTests.CustomSslInfoConfiguration.CustomSslHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.ssl.SslHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.info.SslInfo; +import org.springframework.boot.info.SslInfo.CertificateChainInfo; +import org.springframework.boot.ssl.SslBundles; +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 SslHealthContributorAutoConfiguration}. + * + * @author Jonatan Ivanov + */ +class SslHealthContributorAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(SslHealthContributorAutoConfiguration.class, SslAutoConfiguration.class)) + .withPropertyValues("server.ssl.bundle=ssltest", + "spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks"); + + @Test + void beansShouldNotBeConfigured() { + this.contextRunner.withPropertyValues("management.health.ssl.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(HealthIndicator.class) + .doesNotHaveBean(SslInfo.class)); + } + + @Test + void beansShouldBeConfigured() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(SslHealthIndicator.class); + assertThat(context).hasSingleBean(SslInfo.class); + Health health = context.getBean(SslHealthIndicator.class).health(); + assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE); + assertDetailsKeys(health); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains).first().isInstanceOf(CertificateChainInfo.class); + + }); + } + + @Test + void beansShouldBeConfiguredWithWarningThreshold() { + this.contextRunner.withPropertyValues("management.health.ssl.certificate-validity-warning-threshold=1d") + .run((context) -> { + assertThat(context).hasSingleBean(SslHealthIndicator.class); + assertThat(context).hasSingleBean(SslInfo.class); + assertThat(context).hasSingleBean(SslHealthIndicatorProperties.class); + assertThat(context.getBean(SslHealthIndicatorProperties.class).getCertificateValidityWarningThreshold()) + .isEqualTo(Duration.ofDays(1)); + Health health = context.getBean(SslHealthIndicator.class).health(); + assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE); + assertDetailsKeys(health); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains).first().isInstanceOf(CertificateChainInfo.class); + }); + } + + @Test + void customBeansShouldBeConfigured() { + this.contextRunner.withUserConfiguration(CustomSslInfoConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(SslHealthIndicator.class); + assertThat(context.getBean(SslHealthIndicator.class)) + .isSameAs(context.getBean(CustomSslHealthIndicator.class)); + assertThat(context).hasSingleBean(SslInfo.class); + assertThat(context.getBean(SslInfo.class)).isSameAs(context.getBean("customSslInfo")); + Health health = context.getBean(SslHealthIndicator.class).health(); + assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE); + assertDetailsKeys(health); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains).first().isInstanceOf(CertificateChainInfo.class); + }); + } + + private static void assertDetailsKeys(Health health) { + assertThat(health.getDetails()).containsOnlyKeys("validChains", "invalidChains"); + } + + @SuppressWarnings("unchecked") + private static List getInvalidChains(Health health) { + return (List) health.getDetails().get("invalidChains"); + } + + @Configuration(proxyBeanMethods = false) + static class CustomSslInfoConfiguration { + + @Bean + SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) { + return new CustomSslHealthIndicator(sslInfo); + } + + @Bean + SslInfo customSslInfo(SslBundles sslBundles) { + return new SslInfo(sslBundles, Duration.ofDays(7)); + } + + static class CustomSslHealthIndicator extends SslHealthIndicator { + + CustomSslHealthIndicator(SslInfo sslInfo) { + super(sslInfo); + } + + } + + } + +} 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 5734fbf89b93..61681e3428c0 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 @@ -33,7 +33,10 @@ 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 org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +47,7 @@ * @author Marcin Grzejszczak * @author Moritz Halbritter */ +@ForkedClassPath class BaggagePropagationIntegrationTests { private static final String COUNTRY_CODE = "country-code"; @@ -153,6 +157,7 @@ private void assertMdcValue(String key, String expected) { enum AutoConfig implements Supplier { BRAVE_DEFAULT { + @Override public ApplicationContextRunner get() { return new ApplicationContextRunner() @@ -160,20 +165,24 @@ public ApplicationContextRunner get() { .withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } + }, OTEL_DEFAULT { + @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class)) .withPropertyValues("management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } + }, BRAVE_W3C { + @Override public ApplicationContextRunner get() { return new ApplicationContextRunner() @@ -182,21 +191,25 @@ public ApplicationContextRunner get() { "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } + }, OTEL_W3C { + @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.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"); } + }, BRAVE_B3 { + @Override public ApplicationContextRunner get() { return new ApplicationContextRunner() @@ -205,9 +218,11 @@ public ApplicationContextRunner get() { "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } + }, BRAVE_B3_MULTI { + @Override public ApplicationContextRunner get() { return new ApplicationContextRunner() @@ -216,30 +231,47 @@ public ApplicationContextRunner get() { "management.tracing.baggage.remote-fields=x-vcap-request-id,country-code,bp", "management.tracing.baggage.correlation.fields=country-code,bp"); } + }, OTEL_B3 { + @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.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"); } + }, OTEL_B3_MULTI { + @Override public ApplicationContextRunner get() { - return new ApplicationContextRunner().withConfiguration(AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, - org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.class)) + return new ApplicationContextRunner().withInitializer(new OtelApplicationContextInitializer()) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.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() { @@ -252,4 +284,14 @@ boolean isBrave() { } + static class OtelApplicationContextInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + applicationContext.addApplicationListener(new OpenTelemetryEventPublisherBeansApplicationListener()); + } + + } + } 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 2a066fb3fb13..cc9e1edf8af9 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 @@ -35,6 +35,9 @@ import brave.propagation.Propagation.Factory; import brave.propagation.TraceContext; import brave.sampler.Sampler; +import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Scope; +import io.micrometer.observation.ObservationRegistry; import io.micrometer.tracing.brave.bridge.BraveBaggageManager; import io.micrometer.tracing.brave.bridge.BraveSpanCustomizer; import io.micrometer.tracing.brave.bridge.BraveTracer; @@ -47,6 +50,7 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfigurationTests.SpanHandlerConfiguration.AdditionalSpanHandler; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.IncompatibleConfigurationException; @@ -333,13 +337,13 @@ 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); }); } @@ -354,6 +358,39 @@ void shouldDisablePropagationIfTracingIsDisabled() { }); } + @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"); + }); + } + + @Test + void keysAreSetInBaggage() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)) + .withPropertyValues("management.tracing.baggage.remote-fields=f1,f2") + .run((context) -> { + BraveTracer braveTracer = context.getBean(BraveTracer.class); + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation observation = Observation.start("o1", observationRegistry) + .lowCardinalityKeyValue("f1", "v1") + .highCardinalityKeyValue("f2", "v2"); + Map baggage = braveTracer.getAllBaggage(); + assertThat(baggage).isEmpty(); + try (Scope ignore = observation.openScope()) { + baggage = braveTracer.getAllBaggage(); + assertThat(baggage).containsAllEntriesOf(Map.of("f1", "v1", "f2", "v2")); + } + baggage = braveTracer.getAllBaggage(); + assertThat(baggage).isEmpty(); + }); + } + private void injectToMap(Map map, String key, String value) { map.put(key, value); } 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 3523e0930d42..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 @@ -49,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(brave.propagation.Propagation.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/MicrometerTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java index 5fb5e7d6af8f..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,6 +18,8 @@ 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; @@ -65,6 +67,7 @@ void shouldSupplyBeans() { assertThat(context).hasSingleBean(DefaultNewSpanParser.class); assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class); assertThat(context).hasSingleBean(SpanAspect.class); + assertThat(context).hasSingleBean(SpanTagAnnotationHandler.class); }); } @@ -102,6 +105,8 @@ void shouldBackOffOnCustomBeans() { assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class); assertThat(context).hasBean("customSpanAspect"); assertThat(context).hasSingleBean(SpanAspect.class); + assertThat(context).hasBean("customSpanTagAnnotationHandler"); + assertThat(context).hasSingleBean(SpanTagAnnotationHandler.class); }); } @@ -240,6 +245,12 @@ 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) @@ -247,7 +258,7 @@ private static final class SpanTagAnnotationHandlerConfiguration { @Bean SpanTagAnnotationHandler spanTagAnnotationHandler() { - return new SpanTagAnnotationHandler((aClass) -> null, (aClass) -> null); + 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/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..34a939638fd6 --- /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 deleted file mode 100644 index 2a9bedd7d877..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ /dev/null @@ -1,517 +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; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import io.micrometer.tracing.SpanCustomizer; -import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; -import io.micrometer.tracing.otel.bridge.OtelPropagator; -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.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; -import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.extension.trace.propagation.B3Propagator; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.SpanLimits; -import io.opentelemetry.sdk.trace.SpanProcessor; -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.ResourceAttributes; -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; -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; -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; - -/** - * Tests for {@link OpenTelemetryAutoConfiguration}. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Yanming Zhou - */ -class OpenTelemetryAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class, - OpenTelemetryAutoConfiguration.class)); - - @Test - void shouldSupplyBeans() { - this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(OtelTracer.class); - assertThat(context).hasSingleBean(EventPublisher.class); - assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); - assertThat(context).hasSingleBean(SdkTracerProvider.class); - assertThat(context).hasSingleBean(ContextPropagators.class); - assertThat(context).hasSingleBean(Sampler.class); - assertThat(context).hasSingleBean(Tracer.class); - assertThat(context).hasSingleBean(Slf4JEventListener.class); - assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); - assertThat(context).hasSingleBean(SpanProcessor.class); - 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); - }); - } - - @Test - void samplerIsParentBased() { - this.contextRunner.run((context) -> { - Sampler sampler = context.getBean(Sampler.class); - assertThat(sampler).isNotNull(); - assertThat(sampler.getDescription()).startsWith("ParentBased{"); - }); - } - - @ParameterizedTest - @ValueSource(strings = { "io.micrometer.tracing.otel", "io.opentelemetry.sdk", "io.opentelemetry.api" }) - void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { - this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { - assertThat(context).doesNotHaveBean(OtelTracer.class); - assertThat(context).doesNotHaveBean(EventPublisher.class); - assertThat(context).doesNotHaveBean(OtelCurrentTraceContext.class); - assertThat(context).doesNotHaveBean(SdkTracerProvider.class); - assertThat(context).doesNotHaveBean(ContextPropagators.class); - assertThat(context).doesNotHaveBean(Sampler.class); - assertThat(context).doesNotHaveBean(Tracer.class); - assertThat(context).doesNotHaveBean(Slf4JEventListener.class); - assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); - assertThat(context).doesNotHaveBean(SpanProcessor.class); - 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); - }); - } - - @Test - void shouldBackOffOnCustomBeans() { - this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { - assertThat(context).hasBean("customMicrometerTracer"); - assertThat(context).hasSingleBean(io.micrometer.tracing.Tracer.class); - assertThat(context).hasBean("customEventPublisher"); - assertThat(context).hasSingleBean(EventPublisher.class); - assertThat(context).hasBean("customOtelCurrentTraceContext"); - assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); - assertThat(context).hasBean("customSdkTracerProvider"); - assertThat(context).hasSingleBean(SdkTracerProvider.class); - assertThat(context).hasBean("customContextPropagators"); - assertThat(context).hasSingleBean(ContextPropagators.class); - assertThat(context).hasBean("customSampler"); - assertThat(context).hasSingleBean(Sampler.class); - assertThat(context).hasBean("customTracer"); - assertThat(context).hasSingleBean(Tracer.class); - assertThat(context).hasBean("customSlf4jEventListener"); - assertThat(context).hasSingleBean(Slf4JEventListener.class); - assertThat(context).hasBean("customSlf4jBaggageEventListener"); - assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); - assertThat(context).hasBean("customOtelPropagator"); - 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); - }); - } - - @Test - void shouldSetupDefaultResourceAttributes() { - this.contextRunner - .withConfiguration( - AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)) - .withUserConfiguration(InMemoryRecordingSpanExporterConfiguration.class) - .withPropertyValues("management.tracing.sampling.probability=1.0") - .run((context) -> { - context.getBean(io.micrometer.tracing.Tracer.class).nextSpan().name("test").end(); - InMemoryRecordingSpanExporter exporter = context.getBean(InMemoryRecordingSpanExporter.class); - 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"))) - .getAttributes() - .asMap(); - assertThat(spanData.getResource().getAttributes().asMap()).isEqualTo(expectedAttributes); - }); - } - - @Test - void shouldAllowMultipleSpanProcessors() { - 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); - }); - } - - @Test - void shouldAllowMultipleTextMapPropagators() { - this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { - assertThat(context.getBeansOfType(TextMapPropagator.class)).hasSize(2); - assertThat(context).hasBean("customTextMapPropagator"); - }); - } - - @Test - void shouldNotSupplySlf4jBaggageEventListenerWhenBaggageCorrelationDisabled() { - this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class)); - } - - @Test - void shouldNotSupplySlf4JBaggageEventListenerWhenBaggageDisabled() { - this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class)); - } - - @Test - void shouldSupplyB3PropagationIfPropagationPropertySet() { - this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> { - TextMapPropagator propagator = context.getBean(TextMapPropagator.class); - List injectors = getInjectors(propagator); - assertThat(injectors).hasExactlyElementsOfTypes(B3Propagator.class, BaggageTextMapPropagator.class); - }); - } - - @Test - void shouldSupplyB3PropagationIfPropagationPropertySetAndBaggageDisabled() { - this.contextRunner - .withPropertyValues("management.tracing.propagation.type=B3", "management.tracing.baggage.enabled=false") - .run((context) -> { - TextMapPropagator propagator = context.getBean(TextMapPropagator.class); - List injectors = getInjectors(propagator); - assertThat(injectors).hasExactlyElementsOfTypes(B3Propagator.class); - }); - } - - @Test - void shouldSupplyW3CPropagationWithBaggageByDefault() { - this.contextRunner.withPropertyValues("management.tracing.baggage.remote-fields=foo").run((context) -> { - TextMapPropagator propagator = context.getBean(TextMapPropagator.class); - List injectors = getInjectors(propagator); - List fields = new ArrayList<>(); - for (TextMapPropagator injector : injectors) { - fields.addAll(injector.fields()); - } - assertThat(fields).containsExactly("traceparent", "tracestate", "baggage", "foo"); - }); - } - - @Test - void shouldSupplyW3CPropagationWithoutBaggageWhenDisabled() { - this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false").run((context) -> { - TextMapPropagator propagator = context.getBean(TextMapPropagator.class); - List injectors = getInjectors(propagator); - assertThat(injectors).hasExactlyElementsOfTypes(W3CTraceContextPropagator.class); - }); - } - - @Test - void shouldCustomizeSdkTracerProvider() { - this.contextRunner.withUserConfiguration(SdkTracerProviderCustomizationConfiguration.class).run((context) -> { - SdkTracerProvider tracerProvider = context.getBean(SdkTracerProvider.class); - assertThat(tracerProvider.getSpanLimits().getMaxNumberOfEvents()).isEqualTo(42); - assertThat(tracerProvider.getSampler()).isEqualTo(Sampler.alwaysOn()); - }); - } - - @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); - } - - @Bean - EventPublisher customEventPublisher() { - return mock(EventPublisher.class); - } - - @Bean - OtelCurrentTraceContext customOtelCurrentTraceContext() { - return mock(OtelCurrentTraceContext.class); - } - - @Bean - SdkTracerProvider customSdkTracerProvider() { - return SdkTracerProvider.builder().build(); - } - - @Bean - ContextPropagators customContextPropagators() { - return mock(ContextPropagators.class); - } - - @Bean - Sampler customSampler() { - return mock(Sampler.class); - } - - @Bean - SpanProcessor customSpanProcessor() { - return mock(SpanProcessor.class); - } - - @Bean - Tracer customTracer() { - return mock(Tracer.class); - } - - @Bean - Slf4JEventListener customSlf4jEventListener() { - return new Slf4JEventListener(); - } - - @Bean - Slf4JBaggageEventListener customSlf4jBaggageEventListener() { - return new Slf4JBaggageEventListener(List.of("alpha")); - } - - @Bean - OtelPropagator customOtelPropagator(ContextPropagators propagators, Tracer tracer) { - return new OtelPropagator(propagators, tracer); - } - - @Bean - TextMapPropagator customTextMapPropagator() { - return mock(TextMapPropagator.class); - } - - @Bean - SpanCustomizer customSpanCustomizer() { - return mock(SpanCustomizer.class); - } - - } - - @Configuration(proxyBeanMethods = false) - private static final class SdkTracerProviderCustomizationConfiguration { - - @Bean - @Order(1) - SdkTracerProviderBuilderCustomizer sdkTracerProviderBuilderCustomizerOne() { - return (builder) -> { - SpanLimits spanLimits = SpanLimits.builder().setMaxNumberOfEvents(42).build(); - builder.setSpanLimits(spanLimits); - }; - } - - @Bean - @Order(0) - SdkTracerProviderBuilderCustomizer sdkTracerProviderBuilderCustomizerTwo() { - return (builder) -> { - SpanLimits spanLimits = SpanLimits.builder().setMaxNumberOfEvents(21).build(); - builder.setSpanLimits(spanLimits).setSampler(Sampler.alwaysOn()); - }; - } - - } - - 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 { - - @Bean - InMemoryRecordingSpanExporter spanExporter() { - return new InMemoryRecordingSpanExporter(); - } - - } - - private static final class InMemoryRecordingSpanExporter implements SpanExporter { - - private final List exportedSpans = new ArrayList<>(); - - private final CountDownLatch latch = new CountDownLatch(1); - - @Override - public CompletableResultCode export(Collection spans) { - this.exportedSpans.addAll(spans); - this.latch.countDown(); - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - this.exportedSpans.clear(); - return CompletableResultCode.ofSuccess(); - } - - List getExportedSpans() { - return this.exportedSpans; - } - - void await(Duration timeout) throws InterruptedException, TimeoutException { - if (!this.latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) { - throw new TimeoutException("Waiting for exporting spans timed out (%s)".formatted(timeout)); - } - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublishingContextWrapperBeansTestExecutionListenerIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublishingContextWrapperBeansTestExecutionListenerIntegrationTests.java new file mode 100644 index 000000000000..2daee77b4c77 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryEventPublishingContextWrapperBeansTestExecutionListenerIntegrationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.lang.reflect.Method; +import java.util.List; +import java.util.function.Function; + +import io.opentelemetry.context.ContextStorage; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryEventPublisherBeansApplicationListener.Wrapper.Storage; +import org.springframework.boot.testsupport.classpath.ForkedClassPath; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Integration tests for {@link OpenTelemetryEventPublisherBeansTestExecutionListener}. + * + * @author Phillip Webb + */ +@ForkedClassPath +class OpenTelemetryEventPublishingContextWrapperBeansTestExecutionListenerIntegrationTests { + + private final ContextStorage parent = mock(ContextStorage.class); + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + void wrapperIsInstalled() throws Exception { + Class wrappersClass = Class.forName("io.opentelemetry.context.ContextStorageWrappers"); + Method getWrappersMethod = wrappersClass.getDeclaredMethod("getWrappers"); + getWrappersMethod.setAccessible(true); + List wrappers = (List) getWrappersMethod.invoke(null); + assertThat(wrappers).anyMatch((function) -> function.apply(this.parent) instanceof Storage); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java new file mode 100644 index 000000000000..c38e2fc11313 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryTracingAutoConfigurationTests.java @@ -0,0 +1,580 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import io.micrometer.tracing.SpanCustomizer; +import io.micrometer.tracing.Tracer.SpanInScope; +import io.micrometer.tracing.otel.bridge.EventListener; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelPropagator; +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.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SpanLimits; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; +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; +import org.springframework.boot.context.annotation.Configurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ForkedClassPath; +import org.springframework.context.ConfigurableApplicationContext; +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; +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; + +/** + * Tests for {@link OpenTelemetryTracingAutoConfiguration}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Yanming Zhou + */ +class OpenTelemetryTracingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration.class, + OpenTelemetryTracingAutoConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(OtelTracer.class); + assertThat(context).hasSingleBean(EventPublisher.class); + assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); + assertThat(context).hasSingleBean(SdkTracerProvider.class); + assertThat(context).hasSingleBean(ContextPropagators.class); + assertThat(context).hasSingleBean(Sampler.class); + assertThat(context).hasSingleBean(Tracer.class); + assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); + assertThat(context).hasSingleBean(SpanProcessor.class); + 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); + }); + } + + @Test + void samplerIsParentBased() { + this.contextRunner.run((context) -> { + Sampler sampler = context.getBean(Sampler.class); + assertThat(sampler).isNotNull(); + assertThat(sampler.getDescription()).startsWith("ParentBased{"); + }); + } + + @ParameterizedTest + @ValueSource(strings = { "io.micrometer.tracing.otel", "io.opentelemetry.sdk", "io.opentelemetry.api" }) + void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) { + this.contextRunner.withClassLoader(new FilteredClassLoader(packageName)).run((context) -> { + assertThat(context).doesNotHaveBean(OtelTracer.class); + assertThat(context).doesNotHaveBean(EventPublisher.class); + assertThat(context).doesNotHaveBean(OtelCurrentTraceContext.class); + assertThat(context).doesNotHaveBean(SdkTracerProvider.class); + assertThat(context).doesNotHaveBean(ContextPropagators.class); + assertThat(context).doesNotHaveBean(Sampler.class); + assertThat(context).doesNotHaveBean(Tracer.class); + assertThat(context).doesNotHaveBean(Slf4JEventListener.class); + assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class); + assertThat(context).doesNotHaveBean(SpanProcessor.class); + 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); + }); + } + + @Test + void shouldBackOffOnCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context).hasBean("customMicrometerTracer"); + assertThat(context).hasSingleBean(io.micrometer.tracing.Tracer.class); + assertThat(context).hasBean("customEventPublisher"); + assertThat(context).hasSingleBean(EventPublisher.class); + assertThat(context).hasBean("customOtelCurrentTraceContext"); + assertThat(context).hasSingleBean(OtelCurrentTraceContext.class); + assertThat(context).hasBean("customSdkTracerProvider"); + assertThat(context).hasSingleBean(SdkTracerProvider.class); + assertThat(context).hasBean("customContextPropagators"); + assertThat(context).hasSingleBean(ContextPropagators.class); + assertThat(context).hasBean("customSampler"); + assertThat(context).hasSingleBean(Sampler.class); + assertThat(context).hasBean("customTracer"); + assertThat(context).hasSingleBean(Tracer.class); + assertThat(context).hasBean("customSlf4jEventListener"); + assertThat(context).hasSingleBean(Slf4JEventListener.class); + assertThat(context).hasBean("customSlf4jBaggageEventListener"); + assertThat(context).hasSingleBean(Slf4JBaggageEventListener.class); + assertThat(context).hasBean("customOtelPropagator"); + 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); + }); + } + + @Test + void shouldSetupDefaultResourceAttributes() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)) + .withUserConfiguration(InMemoryRecordingSpanExporterConfiguration.class) + .withPropertyValues("management.tracing.sampling.probability=1.0") + .run((context) -> { + context.getBean(io.micrometer.tracing.Tracer.class).nextSpan().name("test").end(); + InMemoryRecordingSpanExporter exporter = context.getBean(InMemoryRecordingSpanExporter.class); + exporter.await(Duration.ofSeconds(10)); + SpanData spanData = exporter.getExportedSpans().get(0); + Map, Object> expectedAttributes = Resource.getDefault() + .merge(Resource.create(Attributes.of(AttributeKey.stringKey("service.name"), "unknown_service"))) + .getAttributes() + .asMap(); + assertThat(spanData.getResource().getAttributes().asMap()).isEqualTo(expectedAttributes); + }); + } + + @Test + void shouldAllowMultipleSpanProcessors() { + 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); + }); + } + + @Test + void shouldAllowMultipleTextMapPropagators() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { + assertThat(context.getBeansOfType(TextMapPropagator.class)).hasSize(2); + assertThat(context).hasBean("customTextMapPropagator"); + }); + } + + @Test + void shouldNotSupplySlf4jBaggageEventListenerWhenBaggageCorrelationDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.correlation.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class)); + } + + @Test + void shouldNotSupplySlf4JBaggageEventListenerWhenBaggageDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(Slf4JBaggageEventListener.class)); + } + + @Test + void shouldSupplyB3PropagationIfPropagationPropertySet() { + this.contextRunner.withPropertyValues("management.tracing.propagation.type=B3").run((context) -> { + TextMapPropagator propagator = context.getBean(TextMapPropagator.class); + List injectors = getInjectors(propagator); + assertThat(injectors).hasExactlyElementsOfTypes(B3Propagator.class, BaggageTextMapPropagator.class); + }); + } + + @Test + void shouldSupplyB3PropagationIfPropagationPropertySetAndBaggageDisabled() { + this.contextRunner + .withPropertyValues("management.tracing.propagation.type=B3", "management.tracing.baggage.enabled=false") + .run((context) -> { + TextMapPropagator propagator = context.getBean(TextMapPropagator.class); + List injectors = getInjectors(propagator); + assertThat(injectors).hasExactlyElementsOfTypes(B3Propagator.class); + }); + } + + @Test + void shouldSupplyW3CPropagationWithBaggageByDefault() { + this.contextRunner.withPropertyValues("management.tracing.baggage.remote-fields=foo").run((context) -> { + TextMapPropagator propagator = context.getBean(TextMapPropagator.class); + List injectors = getInjectors(propagator); + List fields = new ArrayList<>(); + for (TextMapPropagator injector : injectors) { + fields.addAll(injector.fields()); + } + assertThat(fields).containsExactly("traceparent", "tracestate", "baggage", "foo"); + }); + } + + @Test + void shouldSupplyW3CPropagationWithoutBaggageWhenDisabled() { + this.contextRunner.withPropertyValues("management.tracing.baggage.enabled=false").run((context) -> { + TextMapPropagator propagator = context.getBean(TextMapPropagator.class); + List injectors = getInjectors(propagator); + assertThat(injectors).hasExactlyElementsOfTypes(W3CTraceContextPropagator.class); + }); + } + + @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 + void shouldCustomizeSdkTracerProvider() { + this.contextRunner.withUserConfiguration(SdkTracerProviderCustomizationConfiguration.class).run((context) -> { + SdkTracerProvider tracerProvider = context.getBean(SdkTracerProvider.class); + assertThat(tracerProvider.getSpanLimits().getMaxNumberOfEvents()).isEqualTo(42); + assertThat(tracerProvider.getSampler()).isEqualTo(Sampler.alwaysOn()); + }); + } + + @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(); + }); + } + + @Test // gh-41439 + @ForkedClassPath + void shouldPublishEventsWhenContextStorageIsInitializedEarly() { + this.contextRunner.withInitializer(this::initializeOpenTelemetry) + .withUserConfiguration(OtelEventListener.class) + .run((context) -> { + OtelEventListener listener = context.getBean(OtelEventListener.class); + io.micrometer.tracing.Tracer micrometerTracer = context.getBean(io.micrometer.tracing.Tracer.class); + io.micrometer.tracing.Span span = micrometerTracer.nextSpan().name("test"); + try (SpanInScope scoped = micrometerTracer.withSpan(span.start())) { + assertThat(listener.events).isNotEmpty(); + } + finally { + span.end(); + } + }); + } + + @Test + @SuppressWarnings("removal") + void shouldUseReplacementForDeprecatedVersion() { + Class[] classes = Configurations.getClasses(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)); + assertThat(classes).containsExactly(OpenTelemetryTracingAutoConfiguration.class); + } + + private void initializeOpenTelemetry(ConfigurableApplicationContext context) { + context.addApplicationListener(new OpenTelemetryEventPublisherBeansApplicationListener()); + Span.current(); + } + + 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); + } + + @Bean + EventPublisher customEventPublisher() { + return mock(EventPublisher.class); + } + + @Bean + OtelCurrentTraceContext customOtelCurrentTraceContext() { + return mock(OtelCurrentTraceContext.class); + } + + @Bean + SdkTracerProvider customSdkTracerProvider() { + return SdkTracerProvider.builder().build(); + } + + @Bean + ContextPropagators customContextPropagators() { + return mock(ContextPropagators.class); + } + + @Bean + Sampler customSampler() { + return mock(Sampler.class); + } + + @Bean + SpanProcessor customSpanProcessor() { + return mock(SpanProcessor.class); + } + + @Bean + Tracer customTracer() { + return mock(Tracer.class); + } + + @Bean + Slf4JEventListener customSlf4jEventListener() { + return new Slf4JEventListener(); + } + + @Bean + Slf4JBaggageEventListener customSlf4jBaggageEventListener() { + return new Slf4JBaggageEventListener(List.of("alpha")); + } + + @Bean + OtelPropagator customOtelPropagator(ContextPropagators propagators, Tracer tracer) { + return new OtelPropagator(propagators, tracer); + } + + @Bean + TextMapPropagator customTextMapPropagator() { + return mock(TextMapPropagator.class); + } + + @Bean + SpanCustomizer customSpanCustomizer() { + return mock(SpanCustomizer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class SdkTracerProviderCustomizationConfiguration { + + @Bean + @Order(1) + SdkTracerProviderBuilderCustomizer sdkTracerProviderBuilderCustomizerOne() { + return (builder) -> { + SpanLimits spanLimits = SpanLimits.builder().setMaxNumberOfEvents(42).build(); + builder.setSpanLimits(spanLimits); + }; + } + + @Bean + @Order(0) + SdkTracerProviderBuilderCustomizer sdkTracerProviderBuilderCustomizerTwo() { + return (builder) -> { + SpanLimits spanLimits = SpanLimits.builder().setMaxNumberOfEvents(21).build(); + builder.setSpanLimits(spanLimits).setSampler(Sampler.alwaysOn()); + }; + } + + } + + 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 { + + @Bean + InMemoryRecordingSpanExporter spanExporter() { + return new InMemoryRecordingSpanExporter(); + } + + } + + private static final class InMemoryRecordingSpanExporter implements SpanExporter { + + private final List exportedSpans = new ArrayList<>(); + + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public CompletableResultCode export(Collection spans) { + this.exportedSpans.addAll(spans); + this.latch.countDown(); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + this.exportedSpans.clear(); + return CompletableResultCode.ofSuccess(); + } + + List getExportedSpans() { + return this.exportedSpans; + } + + void await(Duration timeout) throws InterruptedException, TimeoutException { + if (!this.latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) { + throw new TimeoutException("Waiting for exporting spans timed out (%s)".formatted(timeout)); + } + } + + } + + static class OtelEventListener implements EventListener { + + private final List events = new ArrayList<>(); + + @Override + public void onEvent(Object event) { + this.events.add(event); + } + + } + +} 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 deleted file mode 100644 index 0fcc77811f95..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationIntegrationTests.java +++ /dev/null @@ -1,116 +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.tracing.otlp; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; - -import io.micrometer.tracing.Tracer; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.export.SpanExporter; -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.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link OtlpAutoConfiguration}. - * - * @author Jonatan Ivanov - */ -class OtlpAutoConfigurationIntegrationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("management.tracing.sampling.probability=1.0") - .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(); - - @BeforeEach - void setUp() throws IOException { - this.mockWebServer.start(); - } - - @AfterEach - void tearDown() throws IOException { - this.mockWebServer.close(); - } - - @Test - void httpSpanExporterShouldUseProtobufAndNoCompressionByDefault() { - this.mockWebServer.enqueue(new MockResponse()); - this.contextRunner - .withPropertyValues("management.otlp.tracing.endpoint=http://localhost:%d/v1/traces" - .formatted(this.mockWebServer.getPort()), "management.otlp.tracing.headers.custom=42") - .run((context) -> { - context.getBean(Tracer.class).nextSpan().name("test").end(); - assertThat(context.getBean(OtlpHttpSpanExporter.class).flush()) - .isSameAs(CompletableResultCode.ofSuccess()); - RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); - assertThat(request).isNotNull(); - assertThat(request.getRequestLine()).contains("/v1/traces"); - assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf"); - assertThat(request.getHeader("custom")).isEqualTo("42"); - assertThat(request.getBodySize()).isPositive(); - try (Buffer body = request.getBody()) { - assertThat(body.readString(StandardCharsets.UTF_8)).contains("org.springframework.boot"); - } - }); - } - - @Test - void httpSpanExporterCanBeConfiguredToUseGzipCompression() { - this.mockWebServer.enqueue(new MockResponse()); - this.contextRunner - .withPropertyValues("management.otlp.tracing.compression=gzip", - "management.otlp.tracing.endpoint=http://localhost:%d/test".formatted(this.mockWebServer.getPort())) - .run((context) -> { - assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class).hasSingleBean(SpanExporter.class); - context.getBean(Tracer.class).nextSpan().name("test").end(); - assertThat(context.getBean(OtlpHttpSpanExporter.class).flush()) - .isSameAs(CompletableResultCode.ofSuccess()); - RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); - assertThat(request).isNotNull(); - assertThat(request.getRequestLine()).contains("/test"); - 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)); - assertThat(uncompressed.readString(StandardCharsets.UTF_8)).contains("org.springframework.boot"); - } - }); - } - -} 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 deleted file mode 100644 index 9ebebdabfa7f..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java +++ /dev/null @@ -1,159 +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.otlp; - -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; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 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.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") - .run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) - .hasSingleBean(SpanExporter.class)); - } - - @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.contextRunner.withPropertyValues("management.tracing.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); - } - - @Test - void shouldNotSupplyBeansIfTracingBridgeIsMissing() { - this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) - .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); - } - - @Test - void shouldNotSupplyBeansIfOtelSdkIsMissing() { - this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk")) - .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); - } - - @Test - void shouldNotSupplyBeansIfOtelApiIsMissing() { - this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.api")) - .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); - } - - @Test - void shouldNotSupplyBeansIfExporterIsMissing() { - this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter")) - .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); - } - - @Test - void shouldBackOffWhenCustomHttpExporterIsDefined() { - this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class) - .run((context) -> assertThat(context).hasBean("customOtlpHttpSpanExporter") - .hasSingleBean(SpanExporter.class)); - } - - @Test - void shouldBackOffWhenCustomGrpcExporterIsDefined() { - this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class) - .run((context) -> assertThat(context).hasBean("customOtlpGrpcSpanExporter") - .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 { - - @Bean - OtlpHttpSpanExporter customOtlpHttpSpanExporter() { - return OtlpHttpSpanExporter.builder().build(); - } - - } - - @Configuration(proxyBeanMethods = false) - private static final class CustomGrpcExporterConfiguration { - - @Bean - OtlpGrpcSpanExporter customOtlpGrpcSpanExporter() { - return OtlpGrpcSpanExporter.builder().build(); - } - - } - - @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/otlp/OtlpTracingAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..6e69507402d1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationIntegrationTests.java @@ -0,0 +1,215 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import io.micrometer.tracing.Tracer; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okio.Buffer; +import okio.GzipSource; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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.otlp.OtlpTracingAutoConfigurationIntegrationTests.MockGrpcServer.RecordedGrpcRequest; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OtlpTracingAutoConfiguration}. + * + * @author Jonatan Ivanov + */ +class OtlpTracingAutoConfigurationIntegrationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("management.tracing.sampling.probability=1.0") + .withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class, + MicrometerTracingAutoConfiguration.class, OpenTelemetryAutoConfiguration.class, + org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryTracingAutoConfiguration.class, + OtlpTracingAutoConfiguration.class)); + + private final MockWebServer mockWebServer = new MockWebServer(); + + private final MockGrpcServer mockGrpcServer = new MockGrpcServer(); + + @BeforeEach + void startServers() throws Exception { + this.mockWebServer.start(); + this.mockGrpcServer.start(); + } + + @AfterEach + void stopServers() throws Exception { + this.mockWebServer.close(); + this.mockGrpcServer.close(); + } + + @Test + void httpSpanExporterShouldUseProtobufAndNoCompressionByDefault() { + this.mockWebServer.enqueue(new MockResponse()); + this.contextRunner + .withPropertyValues("management.otlp.tracing.endpoint=http://localhost:%d/v1/traces" + .formatted(this.mockWebServer.getPort()), "management.otlp.tracing.headers.custom=42") + .run((context) -> { + context.getBean(Tracer.class).nextSpan().name("test").end(); + assertThat(context.getBean(OtlpHttpSpanExporter.class).flush()) + .isSameAs(CompletableResultCode.ofSuccess()); + RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getRequestLine()).contains("/v1/traces"); + assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf"); + assertThat(request.getHeader("custom")).isEqualTo("42"); + assertThat(request.getBodySize()).isPositive(); + try (Buffer body = request.getBody()) { + assertThat(body.readString(StandardCharsets.UTF_8)).contains("org.springframework.boot"); + } + }); + } + + @Test + void httpSpanExporterCanBeConfiguredToUseGzipCompression() { + this.mockWebServer.enqueue(new MockResponse()); + this.contextRunner + .withPropertyValues("management.otlp.tracing.compression=gzip", + "management.otlp.tracing.endpoint=http://localhost:%d/test".formatted(this.mockWebServer.getPort())) + .run((context) -> { + assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class).hasSingleBean(SpanExporter.class); + context.getBean(Tracer.class).nextSpan().name("test").end(); + assertThat(context.getBean(OtlpHttpSpanExporter.class).flush()) + .isSameAs(CompletableResultCode.ofSuccess()); + RecordedRequest request = this.mockWebServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getRequestLine()).contains("/test"); + 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)); + assertThat(uncompressed.readString(StandardCharsets.UTF_8)).contains("org.springframework.boot"); + } + }); + } + + @Test + void grpcSpanExporterShouldExportSpans() { + this.contextRunner + .withPropertyValues( + "management.otlp.tracing.endpoint=http://localhost:%d".formatted(this.mockGrpcServer.getPort()), + "management.otlp.tracing.headers.custom=42", "management.otlp.tracing.transport=grpc") + .run((context) -> { + context.getBean(Tracer.class).nextSpan().name("test").end(); + assertThat(context.getBean(OtlpGrpcSpanExporter.class).flush()) + .isSameAs(CompletableResultCode.ofSuccess()); + RecordedGrpcRequest request = this.mockGrpcServer.takeRequest(10, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.headers().get("Content-Type")).isEqualTo("application/grpc"); + assertThat(request.headers().get("custom")).isEqualTo("42"); + assertThat(request.bodyAsString()).contains("org.springframework.boot"); + }); + } + + static class MockGrpcServer { + + private final Server server = createServer(); + + private final BlockingQueue recordedRequests = new LinkedBlockingQueue<>(); + + void start() throws Exception { + this.server.start(); + } + + void close() throws Exception { + this.server.stop(); + } + + int getPort() { + return this.server.getURI().getPort(); + } + + RecordedGrpcRequest takeRequest(int timeout, TimeUnit unit) throws InterruptedException { + return this.recordedRequests.poll(timeout, unit); + } + + void recordRequest(RecordedGrpcRequest request) { + this.recordedRequests.add(request); + } + + private Server createServer() { + Server server = new Server(); + server.addConnector(createConnector(server)); + server.setHandler(new GrpcHandler()); + return server; + } + + private ServerConnector createConnector(Server server) { + ServerConnector connector = new ServerConnector(server, + new HTTP2CServerConnectionFactory(new HttpConfiguration())); + connector.setPort(0); + return connector; + } + + class GrpcHandler extends Handler.Abstract { + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + try (InputStream in = Content.Source.asInputStream(request)) { + recordRequest(new RecordedGrpcRequest(request.getHeaders(), in.readAllBytes())); + } + response.getHeaders().add("Content-Type", "application/grpc"); + response.getHeaders().add("Grpc-Status", "0"); + callback.succeeded(); + return true; + } + + } + + record RecordedGrpcRequest(HttpFields headers, byte[] body) { + String bodyAsString() { + return new String(this.body, StandardCharsets.UTF_8); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationTests.java new file mode 100644 index 000000000000..174592638a7d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpTracingAutoConfigurationTests.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.actuate.autoconfigure.tracing.otlp; + +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; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OtlpTracingAutoConfiguration}. + * + * @author Jonatan Ivanov + * @author Moritz Halbritter + * @author Eddú Meléndez + */ +class OtlpTracingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OtlpTracingAutoConfiguration.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 shouldNotSupplyBeansIfGrpcTransportIsEnabledButPropertyIsNotSet() { + this.contextRunner.withPropertyValues("management.otlp.tracing.transport=grpc") + .run((context) -> assertThat(context).doesNotHaveBean(OtlpGrpcSpanExporter.class)); + } + + @Test + void shouldSupplyBeans() { + this.contextRunner.withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4318/v1/traces") + .run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + + @Test + void shouldSupplyBeansIfGrpcTransportIsEnabled() { + this.contextRunner + .withPropertyValues("management.otlp.tracing.endpoint=http://localhost:4317/v1/traces", + "management.otlp.tracing.transport=grpc") + .run((context) -> assertThat(context).hasSingleBean(OtlpGrpcSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + + @Test + 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")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfOtelSdkIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfOtelApiIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.api")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfExporterIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldBackOffWhenCustomHttpExporterIsDefined() { + this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class) + .run((context) -> assertThat(context).hasBean("customOtlpHttpSpanExporter") + .hasSingleBean(SpanExporter.class)); + } + + @Test + void shouldBackOffWhenCustomGrpcExporterIsDefined() { + this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class) + .run((context) -> assertThat(context).hasBean("customOtlpGrpcSpanExporter") + .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 { + + @Bean + OtlpHttpSpanExporter customOtlpHttpSpanExporter() { + return OtlpHttpSpanExporter.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + private static final class CustomGrpcExporterConfiguration { + + @Bean + OtlpGrpcSpanExporter customOtlpGrpcSpanExporter() { + return OtlpGrpcSpanExporter.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ConnectionDetailsConfiguration { + + @Bean + OtlpTracingConnectionDetails otlpTracingConnectionDetails() { + return (transport) -> "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..17466d234946 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({ "deprecation", "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 e03ce27ae223..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 @@ -22,9 +22,9 @@ 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; @@ -50,10 +50,10 @@ class PrometheusExemplarsAutoConfigurationTests { 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}+)\"} .+$"); + "^test_observation_seconds_bucket\\{error=\"none\",le=\".+\"} 1 # \\{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 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", @@ -65,32 +65,59 @@ class PrometheusExemplarsAutoConfigurationTests { @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); + String openMetricsOutput = prometheusMeterRegistry.scrape(OpenMetricsTextFormatWriter.CONTENT_TYPE); assertThat(openMetricsOutput).contains("test_observation_seconds_bucket"); assertThat(openMetricsOutput).containsOnlyOnce("test_observation_seconds_count"); @@ -106,7 +133,7 @@ void prometheusOpenMetricsOutputShouldContainExemplars() { Optional counterTraceInfo = openMetricsOutput.lines() .filter((line) -> line.contains("test_observation_seconds_count") && line.contains("span_id")) - .map(COUNTER_TRACE_INFO_PATTERN::matcher) + .map(COUNT_TRACE_INFO_PATTERN::matcher) .flatMap(Matcher::results) .map((matchResult) -> new TraceInfo(matchResult.group(2), matchResult.group(1))) .findFirst(); @@ -118,11 +145,11 @@ void prometheusOpenMetricsOutputShouldContainExemplars() { @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; } } 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 5b2095344eb7..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 @@ -47,9 +47,6 @@ class WavefrontTracingAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( AutoConfigurations.of(WavefrontAutoConfiguration.class, WavefrontTracingAutoConfiguration.class)); - private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner - .withPropertyValues("management.tracing.enabled=false"); - @Test void shouldSupplyBeans() { this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { @@ -85,12 +82,25 @@ void shouldNotSupplyBeansIfMicrometerReporterWavefrontIsMissing() { } @Test - void shouldNotSupplyBeansIfTracingIsDisabled() { - this.tracingDisabledContextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { - assertThat(context).doesNotHaveBean(WavefrontSpanHandler.class); - assertThat(context).doesNotHaveBean(WavefrontBraveSpanHandler.class); - assertThat(context).doesNotHaveBean(WavefrontOtelSpanExporter.class); - }); + void shouldNotSupplyBeansIfGlobalTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false") + .withUserConfiguration(WavefrontSenderConfiguration.class) + .run((context) -> { + assertThat(context).doesNotHaveBean(WavefrontSpanHandler.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); + }); } @Test 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 1d744175f26b..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,21 +39,21 @@ 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); }); } @@ -66,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)); } @@ -86,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 7874d5ec7730..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,61 +46,127 @@ class ZipkinConfigurationsBraveConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BraveConfiguration.class)); - - private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner - .withPropertyValues("management.tracing.enabled=false"); + .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 shouldNotSupplyZipkinSpanHandlerIfTracingIsDisabled() { - this.tracingDisabledContextRunner.withUserConfiguration(ReporterConfiguration.class) - .run((context) -> assertThat(context).doesNotHaveBean(ZipkinSpanHandler.class)); + 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); } } @@ -103,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(); + } + }; } } @@ -120,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 76d1e3b5d06d..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,30 +40,46 @@ class ZipkinConfigurationsOpenTelemetryConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BaseConfiguration.class, OpenTelemetryConfiguration.class)); - - private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner - .withPropertyValues("management.tracing.enabled=false"); + .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) -> { @@ -74,17 +89,63 @@ void shouldBackOffOnCustomBeans() { } @Test - void shouldNotSupplyZipkinSpanExporterIfTracingIsDisabled() { - this.tracingDisabledContextRunner.withUserConfiguration(SenderConfiguration.class) + 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); } } @@ -100,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..7988fd310837 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({ "deprecation", "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..4dbc69079291 --- /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,171 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.boot.testsupport.classpath.ClassPathExclusions; + +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 + */ +@ClassPathExclusions("spring-web-*.jar") +class ZipkinHttpClientSenderTests extends ZipkinHttpSenderTests { + + private MockWebServer mockBackEnd; + + private String zipkinUrl; + + @Override + @BeforeEach + void beforeEach() { + this.mockBackEnd = new MockWebServer(); + try { + this.mockBackEnd.start(); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + 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"); + }); + } + + @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 66903b9f413e..58b6b934e7a5 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 @@ -18,21 +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; /** @@ -43,9 +36,9 @@ */ abstract class ZipkinHttpSenderTests { - protected Sender sender; + protected BytesMessageSender sender; - abstract Sender createSender(); + abstract BytesMessageSender createSender(); @BeforeEach void beforeEach() { @@ -58,55 +51,14 @@ void afterEach() throws IOException { } @Test - void sendSpansShouldThrowIfCloseWasCalled() throws IOException { + void sendShouldThrowIfCloseWasCalled() throws IOException { this.sender.close(); assertThatExceptionOfType(ClosedSenderException.class) - .isThrownBy(() -> this.sender.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); - } - } - - protected CallbackResult makeAsyncRequest(List encodedSpans) { - return makeAsyncRequest(this.sender, encodedSpans); - } - - protected CallbackResult makeAsyncRequest(Sender sender, List encodedSpans) { - AtomicReference callbackResult = new AtomicReference<>(); - sender.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 { - makeSyncRequest(this.sender, encodedSpans); - } - - protected void makeSyncRequest(Sender sender, List encodedSpans) throws IOException { - sender.sendSpans(encodedSpans).execute(); + .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 ad30c2e5eb52..f753677acc90 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. @@ -23,17 +23,16 @@ 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.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,17 +46,28 @@ * @author Moritz Halbritter * @author Stefan Bratanov */ +@SuppressWarnings({ "deprecation", "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 createSender() { - 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 @@ -68,55 +78,51 @@ void afterEach() throws IOException { } @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.sender.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.sender.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); + @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() @@ -127,7 +133,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 dcfb8616660b..71ad42aa5457 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 @@ -33,10 +33,10 @@ 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.web.reactive.function.client.WebClient; @@ -48,8 +48,11 @@ * * @author Stefan Bratanov */ +@SuppressWarnings({ "deprecation", "removal" }) class ZipkinWebClientSenderTests extends ZipkinHttpSenderTests { + private static final Duration TIMEOUT = Duration.ofSeconds(30); + private static ClearableDispatcher dispatcher; private static MockWebServer mockBackEnd; @@ -79,72 +82,76 @@ void beforeEach() { } @Override - Sender createSender() { - return createSender(Duration.ofSeconds(10)); + BytesMessageSender createSender() { + return createSender(Encoding.JSON, TIMEOUT); + } + + ZipkinWebClientSender createSender(Encoding encoding, Duration timeout) { + return createSender(HttpEndpointSuppliers.constantFactory(), encoding, timeout); } - Sender createSender(Duration timeout) { + ZipkinWebClientSender createSender(HttpEndpointSupplier.Factory endpointSupplierFactory, Encoding encoding, + Duration timeout) { WebClient webClient = WebClient.builder().build(); - return new ZipkinWebClientSender(ZIPKIN_URL, webClient, timeout); + 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.sender.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.sender.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, TIMEOUT)) { + 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"); + @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, TIMEOUT)) { + 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"); @@ -153,19 +160,12 @@ void sendSpansShouldCompressData(boolean async) throws IOException, InterruptedE }); } - @ParameterizedTest - @ValueSource(booleans = { true, false }) - void shouldTimeout(boolean async) { - Sender sender = createSender(Duration.ofMillis(1)); - MockResponse response = new MockResponse().setResponseCode(200).setHeadersDelay(100, TimeUnit.MILLISECONDS); - mockBackEnd.enqueue(response); - if (async) { - CallbackResult callbackResult = makeAsyncRequest(sender, Collections.emptyList()); - assertThat(callbackResult.success()).isFalse(); - assertThat(callbackResult.error()).isInstanceOf(TimeoutException.class); - } - else { - assertThatException().isThrownBy(() -> makeSyncRequest(sender, Collections.emptyList())) + @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); } } 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 512fd8311741..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. @@ -45,17 +45,9 @@ class WavefrontSenderConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(WavefrontSenderConfiguration.class)); - private final ApplicationContextRunner tracingDisabledContextRunner = this.contextRunner - .withPropertyValues("management.tracing.enabled=false"); - private final ApplicationContextRunner metricsDisabledContextRunner = this.contextRunner.withPropertyValues( "management.defaults.metrics.export.enabled=false", "management.simple.metrics.export.enabled=true"); - // Both metrics and tracing are disabled - private final ApplicationContextRunner observabilityDisabledContextRunner = this.contextRunner.withPropertyValues( - "management.tracing.enabled=false", "management.defaults.metrics.export.enabled=false", - "management.simple.metrics.export.enabled=true"); - @Test void shouldNotFailIfWavefrontIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader("com.wavefront")) @@ -98,14 +90,32 @@ void configureWavefrontSender() { } @Test - void shouldNotSupplyWavefrontSenderIfObservabilityIsDisabled() { - this.observabilityDisabledContextRunner.withPropertyValues("management.wavefront.api-token=abcde") + 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 shouldSupplyWavefrontSenderIfOnlyTracingIsDisabled() { - this.tracingDisabledContextRunner.withPropertyValues("management.wavefront.api-token=abcde") + 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)); } 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..8d014fbf38fd 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; @@ -35,7 +36,6 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; @@ -143,7 +143,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()); })); } @@ -214,7 +215,8 @@ String fail() { } - @RestControllerEndpoint(id = "failController") + @org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "failController") + @SuppressWarnings("removal") static class FailingControllerEndpoint { @GetMapping 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 b11756786e27..dd23bd47775a 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -14,6 +14,7 @@ dependencies { 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("com.redis:testcontainers-redis") dockerTestImplementation("org.assertj:assertj-core") dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestImplementation("org.springframework:spring-test") @@ -22,7 +23,7 @@ dependencies { 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") @@ -36,6 +37,8 @@ dependencies { 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") } diff --git a/spring-boot-project/spring-boot-actuator/src/dockerTest/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 index 0202312ba45a..0d200299ff2c 100644 --- a/spring-boot-project/spring-boot-actuator/src/dockerTest/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 @@ -19,6 +19,7 @@ import java.util.UUID; import java.util.function.BiConsumer; +import com.redis.testcontainers.RedisContainer; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; @@ -32,7 +33,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.testsupport.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/ShutdownEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/ShutdownEndpoint.java index 2f19b70cc09c..ac7e036967ec 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/ShutdownEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/ShutdownEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.actuate.context; import org.springframework.beans.BeansException; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; @@ -32,7 +33,7 @@ * @author Andy Wilkinson * @since 2.0.0 */ -@Endpoint(id = "shutdown", enableByDefault = false) +@Endpoint(id = "shutdown", defaultAccess = Access.NONE) public class ShutdownEndpoint implements ApplicationContextAware { private ConfigurableApplicationContext context; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java index 333333f01a7e..96108afa5388 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.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,7 @@ public abstract class AbstractExposableEndpoint implements private final EndpointId id; - private final boolean enabledByDefault; + private final Access defaultAccess; private final List operations; @@ -41,12 +41,26 @@ public abstract class AbstractExposableEndpoint implements * @param id the endpoint id * @param enabledByDefault if the endpoint is enabled by default * @param operations the endpoint operations + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #AbstractExposableEndpoint(EndpointId, Access, Collection)} */ + @Deprecated(since = "3.4.0", forRemoval = true) public AbstractExposableEndpoint(EndpointId id, boolean enabledByDefault, Collection operations) { + this(id, (enabledByDefault) ? Access.UNRESTRICTED : Access.READ_ONLY, operations); + } + + /** + * Create a new {@link AbstractExposableEndpoint} instance. + * @param id the endpoint id + * @param defaultAccess access to the endpoint that is permitted by default + * @param operations the endpoint operations + * @since 3.4.0 + */ + public AbstractExposableEndpoint(EndpointId id, Access defaultAccess, Collection operations) { Assert.notNull(id, "ID must not be null"); Assert.notNull(operations, "Operations must not be null"); this.id = id; - this.enabledByDefault = enabledByDefault; + this.defaultAccess = defaultAccess; this.operations = List.copyOf(operations); } @@ -56,8 +70,15 @@ public EndpointId getEndpointId() { } @Override + @SuppressWarnings("removal") + @Deprecated(since = "3.4.0", forRemoval = true) public boolean isEnableByDefault() { - return this.enabledByDefault; + return this.defaultAccess != Access.NONE; + } + + @Override + public Access getDefaultAccess() { + return this.defaultAccess; } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Access.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Access.java new file mode 100644 index 000000000000..c810d540b20c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Access.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.actuate.endpoint; + +import org.springframework.util.Assert; + +/** + * Permitted level of access to an endpoint and its operations. + * + * @author Andy Wilkinson + * @since 3.4.0 + */ +public enum Access { + + /** + * No access to the endpoint is permitted. + */ + NONE, + + /** + * Read-only access to the endpoint is permitted. + */ + READ_ONLY, + + /** + * Unrestricted access to the endpoint is permitted. + */ + UNRESTRICTED; + + /** + * Cap access to a maximum permitted. + * @param maxPermitted the maximum permitted access + * @return this access if less than the maximum or the maximum permitted + */ + public Access cap(Access maxPermitted) { + Assert.notNull(maxPermitted, "'maxPermittedAccess' must not be null"); + return (ordinal() <= maxPermitted.ordinal()) ? this : maxPermitted; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointAccessResolver.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointAccessResolver.java new file mode 100644 index 000000000000..efb4a58ebad7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointAccessResolver.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.actuate.endpoint; + +/** + * Resolver for the permitted level of {@link Access access} to an endpoint. + * + * @author Andy Wilkinson + * @since 3.4.0 + */ +public interface EndpointAccessResolver { + + /** + * Resolves the permitted level of access for the endpoint with the given + * {@code endpointId} and {@code defaultAccess}. + * @param endpointId the ID of the endpoint + * @param defaultAccess the default access level of the endpoint + * @return the permitted level of access, never {@code null} + */ + Access accessFor(EndpointId endpointId, Access defaultAccess); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java index e535ac00b324..7de3a6ab7d57 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.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,9 +37,19 @@ public interface ExposableEndpoint { /** * Returns if the endpoint is enabled by default. * @return if the endpoint is enabled by default + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #getDefaultAccess()} */ + @Deprecated(since = "3.4.0", forRemoval = true) boolean isEnableByDefault(); + /** + * Returns the access to the endpoint that is permitted by default. + * @return access that is permitted by default + * @since 3.4.0 + */ + Access getDefaultAccess(); + /** * Returns the operations of the endpoint. * @return the operations diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationFilter.java new file mode 100644 index 000000000000..80f226690c83 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationFilter.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.actuate.endpoint; + +/** + * Strategy class that can be used to filter {@link Operation operations}. + * + * @param the operation type + * @author Andy Wilkinson + * @since 3.4.0 + */ +@FunctionalInterface +public interface OperationFilter { + + /** + * Return {@code true} if the filter matches. + * @param endpointId the ID of the endpoint to which the operation belongs + * @param operation the operation to check + * @param defaultAccess the default permitted level of access to the endpoint + * @return {@code true} if the filter matches + */ + boolean match(O operation, EndpointId endpointId, Access defaultAccess); + + /** + * Return an {@link OperationFilter} that filters based on the allowed {@link Access + * access} as determined by an {@link EndpointAccessResolver access resolver}. + * @param the operation type + * @param accessResolver the access resolver + * @return a new {@link OperationFilter} + */ + static OperationFilter byAccess(EndpointAccessResolver accessResolver) { + return (operation, endpointId, defaultAccess) -> { + Access access = accessResolver.accessFor(endpointId, defaultAccess); + return switch (access) { + case NONE -> false; + case READ_ONLY -> operation.getType() == OperationType.READ; + case UNRESTRICTED -> true; + }; + }; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java index 91fa1fa403b6..72a581810331 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.Collection; import org.springframework.boot.actuate.endpoint.AbstractExposableEndpoint; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; @@ -47,10 +48,28 @@ public abstract class AbstractDiscoveredEndpoint extends Ab * @param id the ID of the endpoint * @param enabledByDefault if the endpoint is enabled by default * @param operations the endpoint operations + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #AbstractDiscoveredEndpoint(EndpointDiscoverer, Object, EndpointId, Access, Collection)} */ + @SuppressWarnings("removal") + @Deprecated(since = "3.4.0", forRemoval = true) public AbstractDiscoveredEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { - super(id, enabledByDefault, operations); + this(discoverer, endpointBean, id, (enabledByDefault) ? Access.UNRESTRICTED : Access.READ_ONLY, operations); + } + + /** + * Create a new {@link AbstractDiscoveredEndpoint} instance. + * @param discoverer the discoverer that discovered the endpoint + * @param endpointBean the primary source bean + * @param id the ID of the endpoint + * @param defaultAccess access to the endpoint that is permitted by default + * @param operations the endpoint operations + * @since 3.4.0 + */ + public AbstractDiscoveredEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, + Access defaultAccess, Collection operations) { + super(id, defaultAccess, operations); Assert.notNull(discoverer, "Discoverer must not be null"); Assert.notNull(endpointBean, "EndpointBean must not be null"); this.discoverer = discoverer; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java index 8f8c793e422e..496bf12e801e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.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,6 +23,7 @@ import java.lang.annotation.Target; import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; /** @@ -65,7 +66,16 @@ /** * If the endpoint should be enabled or disabled by default. * @return {@code true} if the endpoint is enabled by default + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of {@link #defaultAccess()} */ + @Deprecated(since = "3.4.0", forRemoval = true) boolean enableByDefault() default true; + /** + * Level of access to the endpoint that is permitted by default. + * @return the default level of access + * @since 3.4.0 + */ + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java index fa77c0dc6947..d28113bbb268 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 +33,13 @@ import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; +import org.springframework.boot.actuate.endpoint.OperationFilter; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -72,7 +74,9 @@ public abstract class EndpointDiscoverer, O exten private final ApplicationContext applicationContext; - private final Collection> filters; + private final Collection> endpointFilters; + + private final Collection> operationFilters; private final DiscoveredOperationsFactory operationsFactory; @@ -85,16 +89,36 @@ public abstract class EndpointDiscoverer, O exten * @param applicationContext the source application context * @param parameterValueMapper the parameter value mapper * @param invokerAdvisors invoker advisors to apply - * @param filters filters to apply + * @param endpointFilters endpoint filters to apply + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #EndpointDiscoverer(ApplicationContext, ParameterValueMapper, Collection, Collection, Collection)} + */ + @Deprecated(since = "3.4.0", forRemoval = true) + public EndpointDiscoverer(ApplicationContext applicationContext, ParameterValueMapper parameterValueMapper, + Collection invokerAdvisors, Collection> endpointFilters) { + this(applicationContext, parameterValueMapper, invokerAdvisors, endpointFilters, Collections.emptyList()); + } + + /** + * Create a new {@link EndpointDiscoverer} instance. + * @param applicationContext the source application context + * @param parameterValueMapper the parameter value mapper + * @param invokerAdvisors invoker advisors to apply + * @param endpointFilters endpoint filters to apply + * @param operationFilters operation filters to apply + * @since 3.4.0 */ public EndpointDiscoverer(ApplicationContext applicationContext, ParameterValueMapper parameterValueMapper, - Collection invokerAdvisors, Collection> filters) { + Collection invokerAdvisors, Collection> endpointFilters, + Collection> operationFilters) { Assert.notNull(applicationContext, "ApplicationContext must not be null"); Assert.notNull(parameterValueMapper, "ParameterValueMapper must not be null"); Assert.notNull(invokerAdvisors, "InvokerAdvisors must not be null"); - Assert.notNull(filters, "Filters must not be null"); + Assert.notNull(endpointFilters, "EndpointFilters must not be null"); + Assert.notNull(operationFilters, "OperationFilters must not be null"); this.applicationContext = applicationContext; - this.filters = Collections.unmodifiableCollection(filters); + this.endpointFilters = Collections.unmodifiableCollection(endpointFilters); + this.operationFilters = Collections.unmodifiableCollection(operationFilters); this.operationsFactory = getOperationsFactory(parameterValueMapper, invokerAdvisors); } @@ -102,6 +126,11 @@ private DiscoveredOperationsFactory getOperationsFactory(ParameterValueMapper Collection invokerAdvisors) { return new DiscoveredOperationsFactory<>(parameterValueMapper, invokerAdvisors) { + @Override + Collection createOperations(EndpointId id, Object target) { + return super.createOperations(id, target); + } + @Override protected O createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { @@ -179,16 +208,31 @@ private Collection convertToEndpoints(Collection endpointBeans) Set endpoints = new LinkedHashSet<>(); for (EndpointBean endpointBean : endpointBeans) { if (isEndpointExposed(endpointBean)) { - endpoints.add(convertToEndpoint(endpointBean)); + E endpoint = convertToEndpoint(endpointBean); + if (isInvocable(endpoint)) { + endpoints.add(endpoint); + } } } return Collections.unmodifiableSet(endpoints); } + /** + * Returns whether the endpoint is invocable and should be included in the discovered + * endpoints. The default implementation returns {@code true} if the endpoint has any + * operations, otherwise {@code false}. + * @param endpoint the endpoint to assess + * @return {@code true} if the endpoint is invocable, otherwise {@code false}. + * @since 3.4.0 + */ + protected boolean isInvocable(E endpoint) { + return !endpoint.getOperations().isEmpty(); + } + private E convertToEndpoint(EndpointBean endpointBean) { MultiValueMap indexed = new LinkedMultiValueMap<>(); EndpointId id = endpointBean.getId(); - addOperations(indexed, id, endpointBean.getBean(), false); + addOperations(indexed, id, endpointBean.getDefaultAccess(), endpointBean.getBean(), false); if (endpointBean.getExtensions().size() > 1) { String extensionBeans = endpointBean.getExtensions() .stream() @@ -198,24 +242,26 @@ private E convertToEndpoint(EndpointBean endpointBean) { + endpointBean.getBeanName() + " (" + extensionBeans + ")"); } for (ExtensionBean extensionBean : endpointBean.getExtensions()) { - addOperations(indexed, id, extensionBean.getBean(), true); + addOperations(indexed, id, endpointBean.getDefaultAccess(), extensionBean.getBean(), true); } assertNoDuplicateOperations(endpointBean, indexed); List operations = indexed.values().stream().map(this::getLast).filter(Objects::nonNull).toList(); - return createEndpoint(endpointBean.getBean(), id, endpointBean.isEnabledByDefault(), operations); + return createEndpoint(endpointBean.getBean(), id, endpointBean.getDefaultAccess(), operations); } - private void addOperations(MultiValueMap indexed, EndpointId id, Object target, - boolean replaceLast) { + private void addOperations(MultiValueMap indexed, EndpointId id, Access defaultAccess, + Object target, boolean replaceLast) { Set replacedLast = new HashSet<>(); Collection operations = this.operationsFactory.createOperations(id, target); for (O operation : operations) { - OperationKey key = createOperationKey(operation); - O last = getLast(indexed.get(key)); - if (replaceLast && replacedLast.add(key) && last != null) { - indexed.get(key).remove(last); + if (!isOperationFiltered(operation, id, defaultAccess)) { + OperationKey key = createOperationKey(operation); + O last = getLast(indexed.get(key)); + if (replaceLast && replacedLast.add(key) && last != null) { + indexed.get(key).remove(last); + } + indexed.add(key, operation); } - indexed.add(key, operation); } } @@ -270,7 +316,7 @@ protected boolean isEndpointTypeExposed(Class beanType) { } private boolean isEndpointFiltered(EndpointBean endpointBean) { - for (EndpointFilter filter : this.filters) { + for (EndpointFilter filter : this.endpointFilters) { if (!isFilterMatch(filter, endpointBean)) { return true; } @@ -307,14 +353,27 @@ private boolean isFilterMatch(EndpointFilter filter, E endpoint) { .get(); } - private E getFilterEndpoint(EndpointBean endpointBean) { - E endpoint = this.filterEndpoints.get(endpointBean); - if (endpoint == null) { - endpoint = createEndpoint(endpointBean.getBean(), endpointBean.getId(), endpointBean.isEnabledByDefault(), - Collections.emptySet()); - this.filterEndpoints.put(endpointBean, endpoint); + private boolean isOperationFiltered(Operation operation, EndpointId endpointId, Access defaultAccess) { + for (OperationFilter filter : this.operationFilters) { + if (!isFilterMatch(filter, operation, endpointId, defaultAccess)) { + return true; + } } - return endpoint; + return false; + } + + @SuppressWarnings("unchecked") + private boolean isFilterMatch(OperationFilter filter, Operation operation, EndpointId endpointId, + Access defaultAccess) { + return LambdaSafe.callback(OperationFilter.class, filter, operation) + .withLogger(EndpointDiscoverer.class) + .invokeAnd((f) -> f.match(operation, endpointId, defaultAccess)) + .get(); + } + + private E getFilterEndpoint(EndpointBean endpointBean) { + return this.filterEndpoints.computeIfAbsent(endpointBean, (key) -> createEndpoint(endpointBean.getBean(), + endpointBean.getId(), endpointBean.getDefaultAccess(), Collections.emptySet())); } @SuppressWarnings("unchecked") @@ -329,8 +388,23 @@ protected Class getEndpointType() { * @param enabledByDefault if the endpoint is enabled by default * @param operations the endpoint operations * @return a created endpoint (a {@link DiscoveredEndpoint} is recommended) + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #createEndpoint(Object, EndpointId, Access, Collection)} + */ + @Deprecated(since = "3.4.0", forRemoval = true) + protected E createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { + return createEndpoint(endpointBean, id, (enabledByDefault) ? Access.UNRESTRICTED : Access.NONE, operations); + } + + /** + * Factory method called to create the {@link ExposableEndpoint endpoint}. + * @param endpointBean the source endpoint bean + * @param id the ID of the endpoint + * @param defaultAccess access to the endpoint that is permitted by default + * @param operations the endpoint operations + * @return a created endpoint (a {@link DiscoveredEndpoint} is recommended) */ - protected abstract E createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, + protected abstract E createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, Collection operations); /** @@ -408,7 +482,7 @@ private static class EndpointBean { private final EndpointId id; - private final boolean enabledByDefault; + private final Access defaultAccess; private final Class filter; @@ -424,7 +498,8 @@ private static class EndpointBean { this.beanType = beanType; this.beanSupplier = beanSupplier; this.id = EndpointId.of(environment, id); - this.enabledByDefault = annotation.getBoolean("enableByDefault"); + boolean enabledByDefault = annotation.getBoolean("enableByDefault"); + this.defaultAccess = enabledByDefault ? annotation.getEnum("defaultAccess", Access.class) : Access.NONE; this.filter = getFilter(beanType); } @@ -459,8 +534,8 @@ EndpointId getId() { return this.id; } - boolean isEnabledByDefault() { - return this.enabledByDefault; + Access getDefaultAccess() { + return this.defaultAccess; } Class getFilter() { 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/jmx/JmxEndpointExporter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java index 8a3445f17436..7c7f147db521 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.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 @@ import org.springframework.jmx.JmxException; import org.springframework.jmx.export.MBeanExportException; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Exports {@link ExposableJmxEndpoint JMX endpoints} to a {@link MBeanServer}. @@ -86,7 +87,11 @@ public void destroy() throws Exception { } private Collection register() { - return this.endpoints.stream().map(this::register).toList(); + return this.endpoints.stream().filter(this::hasOperations).map(this::register).toList(); + } + + private boolean hasOperations(ExposableJmxEndpoint endpoint) { + return !CollectionUtils.isEmpty(endpoint.getOperations()); } private ObjectName register(ExposableJmxEndpoint endpoint) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java index 3cea7f195752..02edac282133 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.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. @@ -18,6 +18,7 @@ import java.util.Collection; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; @@ -31,9 +32,10 @@ */ class DiscoveredJmxEndpoint extends AbstractDiscoveredEndpoint implements ExposableJmxEndpoint { - DiscoveredJmxEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, - boolean enabledByDefault, Collection operations) { - super(discoverer, endpointBean, id, enabledByDefault, operations); + @SuppressWarnings("removal") + DiscoveredJmxEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, Access defaultAccess, + Collection operations) { + super(discoverer, endpointBean, id, defaultAccess, operations); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpoint.java index 0f0d32426eea..ec6761464fd0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpoint.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 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint; import org.springframework.core.annotation.AliasFor; @@ -50,8 +51,18 @@ /** * If the endpoint should be enabled or disabled by default. * @return {@code true} if the endpoint is enabled by default + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of */ + @Deprecated(since = "3.4.0", forRemoval = true) @AliasFor(annotation = Endpoint.class) boolean enableByDefault() default true; + /** + * Level of access to the endpoint that is permitted by default. + * @return the default level of access + * @since 3.4.0 + */ + @AliasFor(annotation = Endpoint.class) + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java index a9843e2af43f..8a2f1bf2b756 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.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,12 +17,15 @@ package org.springframework.boot.actuate.endpoint.jmx.annotation; import java.util.Collection; +import java.util.Collections; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.OperationFilter; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -50,18 +53,37 @@ public class JmxEndpointDiscoverer extends EndpointDiscoverer invokerAdvisors, - Collection> filters) { - super(applicationContext, parameterValueMapper, invokerAdvisors, filters); + Collection> endpointFilters) { + this(applicationContext, parameterValueMapper, invokerAdvisors, endpointFilters, Collections.emptyList()); + } + + /** + * Create a new {@link JmxEndpointDiscoverer} instance. + * @param applicationContext the source application context + * @param parameterValueMapper the parameter value mapper + * @param invokerAdvisors invoker advisors to apply + * @param endpointFilters endpoint filters to apply + * @param operationFilters operation filters to apply + * @since 3.4.0 + */ + public JmxEndpointDiscoverer(ApplicationContext applicationContext, ParameterValueMapper parameterValueMapper, + Collection invokerAdvisors, + Collection> endpointFilters, + Collection> operationFilters) { + super(applicationContext, parameterValueMapper, invokerAdvisors, endpointFilters, operationFilters); } @Override - protected ExposableJmxEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, + protected ExposableJmxEndpoint createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, Collection operations) { - return new DiscoveredJmxEndpoint(this, endpointBean, id, enabledByDefault, operations); + return new DiscoveredJmxEndpoint(this, endpointBean, id, defaultAccess, operations); } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AdditionalPathsMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AdditionalPathsMapper.java new file mode 100644 index 000000000000..0b390c8fb913 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AdditionalPathsMapper.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.actuate.endpoint.web; + +import java.util.List; + +import org.springframework.boot.actuate.endpoint.EndpointId; + +/** + * Strategy interface used to provide a mapping between an endpoint ID and any additional + * paths where it will be exposed. + * + * @author Phillip Webb + * @since 3.4.0 + */ +@FunctionalInterface +public interface AdditionalPathsMapper { + + /** + * Resolve the additional paths for the specified {@code endpointId} and web server + * namespace. + * @param endpointId the id of an endpoint + * @param webServerNamespace the web server namespace + * @return the additional paths of the endpoint or {@code null} if this mapper doesn't + * support the given endpoint ID. + */ + List getAdditionalPaths(EndpointId endpointId, WebServerNamespace webServerNamespace); + +} 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/PathMappedEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoint.java index 0a39892c9123..f75f9f1207cd 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoint.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,9 @@ package org.springframework.boot.actuate.endpoint.web; +import java.util.Collections; +import java.util.List; + import org.springframework.boot.actuate.endpoint.ExposableEndpoint; /** @@ -30,11 +33,23 @@ public interface PathMappedEndpoint { /** - * Return the root path of the endpoint, relative to the context that exposes it. For - * example, a root path of {@code example} would be exposed under the URL - * "/{actuator-context}/example". + * Return the root path of the endpoint (relative to the context and base path) that + * exposes it. For example, a root path of {@code example} would be exposed under the + * URL "/{actuator-context}/example". * @return the root path for the endpoint + * @see PathMappedEndpoints#getBasePath */ String getRootPath(); + /** + * Return any additional paths (relative to the context) for the given + * {@link WebServerNamespace}. + * @param webServerNamespace the web server namespace + * @return a list of additional paths + * @since 3.4.0 + */ + default List getAdditionalPaths(WebServerNamespace webServerNamespace) { + return Collections.emptyList(); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java index c8be88751b18..96995b2e0daf 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java @@ -20,12 +20,14 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.stream.Stream; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * A collection of {@link PathMappedEndpoint path mapped endpoints}. @@ -101,7 +103,7 @@ public String getPath(EndpointId endpointId) { } /** - * Return the root paths for each mapped endpoint. + * Return the root paths for each mapped endpoint (excluding additional paths). * @return all root paths */ public Collection getAllRootPaths() { @@ -109,13 +111,36 @@ public Collection getAllRootPaths() { } /** - * Return the full paths for each mapped endpoint. + * Return the full paths for each mapped endpoint (excluding additional paths). * @return all root paths */ public Collection getAllPaths() { return stream().map(this::getPath).toList(); } + /** + * Return the additional paths for each mapped endpoint. + * @param webServerNamespace the web server namespace + * @param endpointId the endpoint ID + * @return all additional paths + * @since 3.4.0 + */ + public Collection getAdditionalPaths(WebServerNamespace webServerNamespace, EndpointId endpointId) { + return getAdditionalPaths(webServerNamespace, getEndpoint(endpointId)).toList(); + } + + private Stream getAdditionalPaths(WebServerNamespace webServerNamespace, PathMappedEndpoint endpoint) { + List additionalPaths = (endpoint != null) ? endpoint.getAdditionalPaths(webServerNamespace) : null; + if (CollectionUtils.isEmpty(additionalPaths)) { + return Stream.empty(); + } + return additionalPaths.stream().map(this::getAdditionalPath); + } + + private String getAdditionalPath(String path) { + return path.startsWith("/") ? path : "/" + path; + } + /** * Return the {@link PathMappedEndpoint} with the given ID or {@code null} if the * endpoint cannot be found. 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..13508e7c87b8 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. @@ -16,14 +16,27 @@ package org.springframework.boot.actuate.endpoint.web; +import java.io.IOException; import java.util.Collection; +import java.util.EnumSet; +import java.util.Locale; +import java.util.Set; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRegistration.Dynamic; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -35,19 +48,32 @@ * @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 Set READ_ONLY_ACCESS_REQUEST_METHODS = Set.of("GET", "HEAD"); + private static final Log logger = LogFactory.getLog(ServletEndpointRegistrar.class); private final String basePath; private final Collection servletEndpoints; + private final EndpointAccessResolver endpointAccessResolver; + public ServletEndpointRegistrar(String basePath, Collection servletEndpoints) { + this(basePath, servletEndpoints, (endpointId, defaultAccess) -> Access.NONE); + } + + public ServletEndpointRegistrar(String basePath, Collection servletEndpoints, + EndpointAccessResolver endpointAccessResolver) { Assert.notNull(servletEndpoints, "ServletEndpoints must not be null"); this.basePath = cleanBasePath(basePath); this.servletEndpoints = servletEndpoints; + this.endpointAccessResolver = endpointAccessResolver; } private static String cleanBasePath(String basePath) { @@ -63,6 +89,10 @@ public void onStartup(ServletContext servletContext) throws ServletException { } private void register(ServletContext servletContext, ExposableServletEndpoint endpoint) { + Access access = this.endpointAccessResolver.accessFor(endpoint.getEndpointId(), endpoint.getDefaultAccess()); + if (access == Access.NONE) { + return; + } String name = endpoint.getEndpointId().toLowerCaseString() + "-actuator-endpoint"; String path = this.basePath + "/" + endpoint.getRootPath(); String urlMapping = path.endsWith("/") ? path + "*" : path + "/*"; @@ -71,7 +101,43 @@ private void register(ServletContext servletContext, ExposableServletEndpoint en registration.addMapping(urlMapping); registration.setInitParameters(endpointServlet.getInitParameters()); registration.setLoadOnStartup(endpointServlet.getLoadOnStartup()); + if (access == Access.READ_ONLY) { + servletContext.addFilter(name + "-access-filter", new ReadOnlyAccessFilter()) + .addMappingForServletNames(EnumSet.allOf(DispatcherType.class), false, name); + } logger.info("Registered '" + path + "' to " + name); } + static class ReadOnlyAccessFilter implements Filter { + + private static final int METHOD_NOT_ALLOWED = 405; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (request instanceof HttpServletRequest httpRequest + && response instanceof HttpServletResponse httpResponse) { + doFilter(httpRequest, httpResponse, chain); + } + else { + throw new ServletException(); + } + } + + private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (isReadOnlyAccessMethod(request)) { + chain.doFilter(request, response); + } + else { + response.sendError(METHOD_NOT_ALLOWED); + } + } + + private boolean isReadOnlyAccessMethod(HttpServletRequest request) { + return READ_ONLY_ACCESS_REQUEST_METHODS.contains(request.getMethod().toUpperCase(Locale.ROOT)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java index 97b1ccee36d0..f0637492d01e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.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,7 +19,8 @@ import org.springframework.util.StringUtils; /** - * Enumeration of server namespaces. + * A web server namespace used for disambiguation when multiple web servers are running in + * the same application (for example a management context running on a different port). * * @author Phillip Webb * @author Madhura Bhave @@ -43,17 +44,14 @@ private WebServerNamespace(String value) { this.value = value; } + /** + * Return the value of the namespace. + * @return the value + */ public String getValue() { return this.value; } - public static WebServerNamespace from(String value) { - if (StringUtils.hasText(value)) { - return new WebServerNamespace(value); - } - return SERVER; - } - @Override public boolean equals(Object obj) { if (this == obj) { @@ -71,4 +69,22 @@ public int hashCode() { return this.value.hashCode(); } + @Override + public String toString() { + return this.value; + } + + /** + * Factory method to create a new {@link WebServerNamespace} from a value. If the + * value is empty or {@code null} then {@link #SERVER} is returned. + * @param value the namespace value or {@code null} + * @return the web server namespace + */ + public static WebServerNamespace from(String value) { + if (StringUtils.hasText(value)) { + return new WebServerNamespace(value); + } + return SERVER; + } + } 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..02aed521ae33 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. @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint; @@ -47,12 +48,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 { /** @@ -69,4 +72,12 @@ @AliasFor(annotation = Endpoint.class) boolean enableByDefault() default true; + /** + * Level of access to the endpoint that is permitted by default. + * @return the default level of access + * @since 3.4.0 + */ + @AliasFor(annotation = Endpoint.class) + Access defaultAccess() default Access.UNRESTRICTED; + } 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..08f30dbe2265 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. @@ -23,6 +23,7 @@ import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; @@ -31,7 +32,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 +43,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 { @@ -58,7 +61,7 @@ public class ControllerEndpointDiscoverer extends EndpointDiscoverer endpointPathMappers, Collection> filters) { - super(applicationContext, ParameterValueMapper.NONE, Collections.emptyList(), filters); + super(applicationContext, ParameterValueMapper.NONE, Collections.emptyList(), filters, Collections.emptyList()); this.endpointPathMappers = endpointPathMappers; } @@ -69,10 +72,10 @@ protected boolean isEndpointTypeExposed(Class beanType) { } @Override - protected ExposableControllerEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, + protected ExposableControllerEndpoint createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, Collection operations) { String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id); - return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath, enabledByDefault); + return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath, defaultAccess); } @Override @@ -86,6 +89,11 @@ protected OperationKey createOperationKey(Operation operation) { throw new IllegalStateException("ControllerEndpoints must not declare operations"); } + @Override + protected boolean isInvocable(ExposableControllerEndpoint endpoint) { + return true; + } + static class ControllerEndpointDiscovererRuntimeHints implements RuntimeHintsRegistrar { @Override 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/ControllerEndpointsSupplier.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointsSupplier.java index d33a1234d219..72b889384468 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointsSupplier.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointsSupplier.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,8 +23,11 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.3 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ @FunctionalInterface +@Deprecated(since = "3.3.3", forRemoval = true) +@SuppressWarnings("removal") public interface ControllerEndpointsSupplier extends EndpointsSupplier { } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java index be4912db124a..a3b1793a35a6 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.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. @@ -18,6 +18,7 @@ import java.util.Collections; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; @@ -28,14 +29,15 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") class DiscoveredControllerEndpoint extends AbstractDiscoveredEndpoint implements ExposableControllerEndpoint { private final String rootPath; DiscoveredControllerEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, - String rootPath, boolean enabledByDefault) { - super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); + String rootPath, Access defaultAccess) { + super(discoverer, endpointBean, id, defaultAccess, Collections.emptyList()); this.rootPath = rootPath; } 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..e7b45d2928d6 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. @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.function.Supplier; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; @@ -32,6 +33,7 @@ * * @author Phillip Webb */ +@SuppressWarnings("removal") class DiscoveredServletEndpoint extends AbstractDiscoveredEndpoint implements ExposableServletEndpoint { private final String rootPath; @@ -39,8 +41,8 @@ class DiscoveredServletEndpoint extends AbstractDiscoveredEndpoint im private final EndpointServlet endpointServlet; DiscoveredServletEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, String rootPath, - boolean enabledByDefault) { - super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); + Access defaultAccess) { + super(discoverer, endpointBean, id, defaultAccess, Collections.emptyList()); String beanType = endpointBean.getClass().getName(); Assert.state(endpointBean instanceof Supplier, () -> "ServletEndpoint bean " + beanType + " must be a supplier"); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java index b62f4f6dda92..5850e0318dc8 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.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. @@ -17,12 +17,17 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; /** * A discovered {@link ExposableWebEndpoint web endpoint}. @@ -33,10 +38,14 @@ class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint imp private final String rootPath; + private Collection additionalPathsMappers; + DiscoveredWebEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, String rootPath, - boolean enabledByDefault, Collection operations) { - super(discoverer, endpointBean, id, enabledByDefault, operations); + Access defaultAccess, Collection operations, + Collection additionalPathsMappers) { + super(discoverer, endpointBean, id, defaultAccess, operations); this.rootPath = rootPath; + this.additionalPathsMappers = additionalPathsMappers; } @Override @@ -44,4 +53,15 @@ public String getRootPath() { return this.rootPath; } + @Override + public List getAdditionalPaths(WebServerNamespace webServerNamespace) { + return this.additionalPathsMappers.stream() + .flatMap((mapper) -> getAdditionalPaths(webServerNamespace, mapper)) + .toList(); + } + + private Stream getAdditionalPaths(WebServerNamespace webServerNamespace, AdditionalPathsMapper mapper) { + return mapper.getAdditionalPaths(getEndpointId(), webServerNamespace).stream(); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ExposableControllerEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ExposableControllerEndpoint.java index f86c28199e72..814d8eceb42a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ExposableControllerEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ExposableControllerEndpoint.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,7 +28,9 @@ * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.3 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.3", forRemoval = true) public interface ExposableControllerEndpoint extends ExposableEndpoint, PathMappedEndpoint { /** 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..549f8e1a58bf 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. @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint; @@ -48,6 +49,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 +57,7 @@ @Endpoint @FilteredEndpoint(ControllerEndpointFilter.class) @ResponseBody +@Deprecated(since = "3.3.0", forRemoval = true) public @interface RestControllerEndpoint { /** @@ -71,4 +74,12 @@ @AliasFor(annotation = Endpoint.class) boolean enableByDefault() default true; + /** + * Level of access to the endpoint that is permitted by default. + * @return the default level of access + * @since 3.4.0 + */ + @AliasFor(annotation = Endpoint.class) + Access defaultAccess() default Access.UNRESTRICTED; + } 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..ca1480015c96 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. @@ -23,6 +23,7 @@ import java.lang.annotation.Target; import java.util.function.Supplier; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint; import org.springframework.boot.actuate.endpoint.web.EndpointServlet; @@ -40,12 +41,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 { /** @@ -62,4 +65,12 @@ @AliasFor(annotation = Endpoint.class) boolean enableByDefault() default true; + /** + * Level of access to the endpoint that is permitted by default. + * @return the default level of access + * @since 3.4.0 + */ + @AliasFor(annotation = Endpoint.class) + Access defaultAccess() default Access.UNRESTRICTED; + } 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..9d8804af0ced 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. @@ -23,6 +23,7 @@ import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; @@ -32,7 +33,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 +43,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 { @@ -58,7 +61,7 @@ public class ServletEndpointDiscoverer extends EndpointDiscoverer endpointPathMappers, Collection> filters) { - super(applicationContext, ParameterValueMapper.NONE, Collections.emptyList(), filters); + super(applicationContext, ParameterValueMapper.NONE, Collections.emptyList(), filters, Collections.emptyList()); this.endpointPathMappers = endpointPathMappers; } @@ -68,10 +71,10 @@ protected boolean isEndpointTypeExposed(Class beanType) { } @Override - protected ExposableServletEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, + protected ExposableServletEndpoint createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, Collection operations) { String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id); - return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, enabledByDefault); + return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, defaultAccess); } @Override @@ -85,6 +88,11 @@ protected OperationKey createOperationKey(Operation operation) { throw new IllegalStateException("ServletEndpoints must not declare operations"); } + @Override + protected boolean isInvocable(ExposableServletEndpoint endpoint) { + return true; + } + static class ServletEndpointDiscovererRuntimeHints implements RuntimeHintsRegistrar { @Override 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/endpoint/web/annotation/WebEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpoint.java index 26a1c69a0458..adca3f24d477 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpoint.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 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint; import org.springframework.core.annotation.AliasFor; @@ -50,8 +51,18 @@ /** * If the endpoint should be enabled or disabled by default. * @return {@code true} if the endpoint is enabled by default + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of */ + @Deprecated(since = "3.4.0", forRemoval = true) @AliasFor(annotation = Endpoint.class) boolean enableByDefault() default true; + /** + * Level of access to the endpoint that is permitted by default. + * @return the default level of access + * @since 3.4.0 + */ + @AliasFor(annotation = Endpoint.class) + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java index 7b6dc7f510e9..9d398212f152 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.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,18 +17,22 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.OperationFilter; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; @@ -51,6 +55,8 @@ public class WebEndpointDiscoverer extends EndpointDiscoverer endpointPathMappers; + private final List additionalPathsMappers; + private final RequestPredicateFactory requestPredicateFactory; /** @@ -61,21 +67,48 @@ public class WebEndpointDiscoverer extends EndpointDiscoverer endpointPathMappers, Collection invokerAdvisors, Collection> filters) { - super(applicationContext, parameterValueMapper, invokerAdvisors, filters); - this.endpointPathMappers = endpointPathMappers; + this(applicationContext, parameterValueMapper, endpointMediaTypes, endpointPathMappers, Collections.emptyList(), + invokerAdvisors, filters, Collections.emptyList()); + } + + /** + * Create a new {@link WebEndpointDiscoverer} instance. + * @param applicationContext the source application context + * @param parameterValueMapper the parameter value mapper + * @param endpointMediaTypes the endpoint media types + * @param endpointPathMappers the endpoint path mappers + * @param additionalPathsMappers the + * @param invokerAdvisors invoker advisors to apply + * @param endpointFilters endpoint filters to apply + * @param operationFilters operation filters to apply + * @since 3.4.0 + */ + public WebEndpointDiscoverer(ApplicationContext applicationContext, ParameterValueMapper parameterValueMapper, + EndpointMediaTypes endpointMediaTypes, List endpointPathMappers, + List additionalPathsMappers, Collection invokerAdvisors, + Collection> endpointFilters, + Collection> operationFilters) { + super(applicationContext, parameterValueMapper, invokerAdvisors, endpointFilters, operationFilters); + this.endpointPathMappers = (endpointPathMappers != null) ? endpointPathMappers : Collections.emptyList(); + this.additionalPathsMappers = (additionalPathsMappers != null) ? additionalPathsMappers + : Collections.emptyList(); this.requestPredicateFactory = new RequestPredicateFactory(endpointMediaTypes); } @Override - protected ExposableWebEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, + protected ExposableWebEndpoint createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, Collection operations) { String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id); - return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath, enabledByDefault, operations); + return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath, defaultAccess, operations, + this.additionalPathsMappers); } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java index 3cb0cf56e0a6..8162d33155c4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java @@ -57,6 +57,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.authorization.AuthorityAuthorizationManager; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.util.AntPathMatcher; @@ -523,9 +524,9 @@ public Principal getPrincipal() { @Override public boolean isUserInRole(String role) { String authority = (!role.startsWith(ROLE_PREFIX)) ? ROLE_PREFIX + role : role; - return AuthorityAuthorizationManager.hasAuthority(authority) - .check(this::getAuthentication, null) - .isGranted(); + AuthorizationResult result = AuthorityAuthorizationManager.hasAuthority(authority) + .authorize(this::getAuthentication, null); + return result != null && result.isGranted(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AdditionalHealthEndpointPathsWebFluxHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AdditionalHealthEndpointPathsWebFluxHandlerMapping.java index 464842616702..93f742886935 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AdditionalHealthEndpointPathsWebFluxHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AdditionalHealthEndpointPathsWebFluxHandlerMapping.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.actuate.endpoint.web.reactive; +import java.util.Collection; import java.util.Collections; import java.util.Set; @@ -41,21 +42,28 @@ public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends Abstract private final EndpointMapping endpointMapping; - private final ExposableWebEndpoint endpoint; + private final ExposableWebEndpoint healthEndpoint; private final Set groups; public AdditionalHealthEndpointPathsWebFluxHandlerMapping(EndpointMapping endpointMapping, - ExposableWebEndpoint endpoint, Set groups) { - super(endpointMapping, Collections.singletonList(endpoint), null, null, false); + ExposableWebEndpoint healthEndpoint, Set groups) { + super(endpointMapping, asList(healthEndpoint), null, null, false); this.endpointMapping = endpointMapping; this.groups = groups; - this.endpoint = endpoint; + this.healthEndpoint = healthEndpoint; + } + + private static Collection asList(ExposableWebEndpoint healthEndpoint) { + return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList(); } @Override protected void initHandlerMethods() { - for (WebOperation operation : this.endpoint.getOperations()) { + if (this.healthEndpoint == null) { + return; + } + for (WebOperation operation : this.healthEndpoint.getOperations()) { WebOperationRequestPredicate predicate = operation.getRequestPredicate(); String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable(); if (matchAllRemainingPathSegmentsVariable != null) { @@ -64,7 +72,7 @@ protected void initHandlerMethods() { if (additionalPath != null) { RequestMappingInfo requestMappingInfo = getRequestMappingInfo(operation, additionalPath.getValue()); - registerReadMapping(requestMappingInfo, this.endpoint, operation); + registerReadMapping(requestMappingInfo, this.healthEndpoint, operation); } } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMapping.java index e326a9bada6b..8591199e0b16 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,15 +19,19 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.result.method.RequestMappingInfo; @@ -35,21 +39,31 @@ import org.springframework.web.util.pattern.PathPattern; /** - * {@link HandlerMapping} that exposes {@link ControllerEndpoint @ControllerEndpoint} and - * {@link RestControllerEndpoint @RestControllerEndpoint} annotated endpoints over Spring - * WebFlux. + * {@link HandlerMapping} that exposes + * {@link org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint @ControllerEndpoint} + * and + * {@link org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint @RestControllerEndpoint} + * annotated endpoints over Spring WebFlux. * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.5", forRemoval = true) +@SuppressWarnings("removal") public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMapping { + private static final Set READ_ONLY_ACCESS_REQUEST_METHODS = EnumSet.of(RequestMethod.GET, + RequestMethod.HEAD); + private final EndpointMapping endpointMapping; private final CorsConfiguration corsConfiguration; private final Map handlers; + private final EndpointAccessResolver accessResolver; + /** * Create a new {@link ControllerEndpointHandlerMapping} instance providing mappings * for the specified endpoints. @@ -59,11 +73,26 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi */ public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints, CorsConfiguration corsConfiguration) { + this(endpointMapping, endpoints, corsConfiguration, (endpointId, defaultAccess) -> Access.NONE); + } + + /** + * Create a new {@link ControllerEndpointHandlerMapping} instance providing mappings + * for the specified endpoints. + * @param endpointMapping the base mapping for all endpoints + * @param endpoints the web endpoints + * @param corsConfiguration the CORS configuration for the endpoints or {@code null} + * @param endpointAccessResolver resolver for endpoint access + */ + public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping, + Collection endpoints, CorsConfiguration corsConfiguration, + EndpointAccessResolver endpointAccessResolver) { Assert.notNull(endpointMapping, "EndpointMapping must not be null"); Assert.notNull(endpoints, "Endpoints must not be null"); this.endpointMapping = endpointMapping; this.handlers = getHandlers(endpoints); this.corsConfiguration = corsConfiguration; + this.accessResolver = endpointAccessResolver; setOrder(-100); } @@ -81,10 +110,31 @@ protected void initHandlerMethods() { @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { ExposableControllerEndpoint endpoint = this.handlers.get(handler); + Access access = this.accessResolver.accessFor(endpoint.getEndpointId(), endpoint.getDefaultAccess()); + if (access == Access.NONE) { + return; + } + if (access == Access.READ_ONLY) { + mapping = withReadOnlyAccess(access, mapping); + if (CollectionUtils.isEmpty(mapping.getMethodsCondition().getMethods())) { + return; + } + } mapping = withEndpointMappedPatterns(endpoint, mapping); super.registerHandlerMethod(handler, method, mapping); } + private RequestMappingInfo withReadOnlyAccess(Access access, RequestMappingInfo mapping) { + Set methods = new HashSet<>(mapping.getMethodsCondition().getMethods()); + if (methods.isEmpty()) { + methods.addAll(READ_ONLY_ACCESS_REQUEST_METHODS); + } + else { + methods.retainAll(READ_ONLY_ACCESS_REQUEST_METHODS); + } + return mapping.mutate().methods(methods.toArray(new RequestMethod[0])).build(); + } + private RequestMappingInfo withEndpointMappedPatterns(ExposableControllerEndpoint endpoint, RequestMappingInfo mapping) { Set patterns = mapping.getPatternsCondition().getPatterns(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java index 48b717cb343f..6b3573f47af4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AdditionalHealthEndpointPathsWebMvcHandlerMapping.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.actuate.endpoint.web.servlet; +import java.util.Collection; import java.util.Collections; import java.util.Set; @@ -36,27 +37,34 @@ */ public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractWebMvcEndpointHandlerMapping { - private final ExposableWebEndpoint endpoint; + private final ExposableWebEndpoint healthEndpoint; private final Set groups; - public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint endpoint, + public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint healthEndpoint, Set groups) { - super(new EndpointMapping(""), Collections.singletonList(endpoint), null, false); - this.endpoint = endpoint; + super(new EndpointMapping(""), asList(healthEndpoint), null, false); + this.healthEndpoint = healthEndpoint; this.groups = groups; } + private static Collection asList(ExposableWebEndpoint healthEndpoint) { + return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList(); + } + @Override protected void initHandlerMethods() { - for (WebOperation operation : this.endpoint.getOperations()) { + if (this.healthEndpoint == null) { + return; + } + for (WebOperation operation : this.healthEndpoint.getOperations()) { WebOperationRequestPredicate predicate = operation.getRequestPredicate(); String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable(); if (matchAllRemainingPathSegmentsVariable != null) { for (HealthEndpointGroup group : this.groups) { AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath(); if (additionalPath != null) { - registerMapping(this.endpoint, predicate, operation, additionalPath.getValue()); + registerMapping(this.healthEndpoint, predicate, operation, additionalPath.getValue()); } } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMapping.java index 3da646413dbe..b7085b91717e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,20 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; -import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; @@ -36,21 +40,31 @@ import org.springframework.web.util.pattern.PathPattern; /** - * {@link HandlerMapping} that exposes {@link ControllerEndpoint @ControllerEndpoint} and - * {@link RestControllerEndpoint @RestControllerEndpoint} annotated endpoints over Spring - * MVC. + * {@link HandlerMapping} that exposes + * {@link org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint @ControllerEndpoint} + * and + * {@link org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint @RestControllerEndpoint} + * annotated endpoints over Spring MVC. * * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.5", forRemoval = true) +@SuppressWarnings("removal") public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMapping { + private static final Set READ_ONLY_ACCESS_REQUEST_METHODS = EnumSet.of(RequestMethod.GET, + RequestMethod.HEAD); + private final EndpointMapping endpointMapping; private final CorsConfiguration corsConfiguration; private final Map handlers; + private final EndpointAccessResolver accessResolver; + /** * Create a new {@link ControllerEndpointHandlerMapping} instance providing mappings * for the specified endpoints. @@ -60,11 +74,26 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi */ public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints, CorsConfiguration corsConfiguration) { + this(endpointMapping, endpoints, corsConfiguration, (endpointId, defaultAccess) -> Access.NONE); + } + + /** + * Create a new {@link ControllerEndpointHandlerMapping} instance providing mappings + * for the specified endpoints. + * @param endpointMapping the base mapping for all endpoints + * @param endpoints the web endpoints + * @param corsConfiguration the CORS configuration for the endpoints or {@code null} + * @param endpointAccessResolver resolver for endpoint access + */ + public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping, + Collection endpoints, CorsConfiguration corsConfiguration, + EndpointAccessResolver endpointAccessResolver) { Assert.notNull(endpointMapping, "EndpointMapping must not be null"); Assert.notNull(endpoints, "Endpoints must not be null"); this.endpointMapping = endpointMapping; this.handlers = getHandlers(endpoints); this.corsConfiguration = corsConfiguration; + this.accessResolver = endpointAccessResolver; setOrder(-100); } @@ -82,10 +111,32 @@ protected void initHandlerMethods() { @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { ExposableControllerEndpoint endpoint = this.handlers.get(handler); + Access access = this.accessResolver.accessFor(endpoint.getEndpointId(), endpoint.getDefaultAccess()); + if (access == Access.NONE) { + return; + } + if (access == Access.READ_ONLY) { + mapping = withReadOnlyAccess(access, mapping); + if (CollectionUtils.isEmpty(mapping.getMethodsCondition().getMethods())) { + return; + } + } mapping = withEndpointMappedPatterns(endpoint, mapping); super.registerHandlerMethod(handler, method, mapping); } + private RequestMappingInfo withReadOnlyAccess(Access access, RequestMappingInfo mapping) { + Set methods = mapping.getMethodsCondition().getMethods(); + Set modifiedMethods = new HashSet<>(methods); + if (modifiedMethods.isEmpty()) { + modifiedMethods.addAll(READ_ONLY_ACCESS_REQUEST_METHODS); + } + else { + modifiedMethods.retainAll(READ_ONLY_ACCESS_REQUEST_METHODS); + } + return mapping.mutate().methods(modifiedMethods.toArray(new RequestMethod[0])).build(); + } + private RequestMappingInfo withEndpointMappedPatterns(ExposableControllerEndpoint endpoint, RequestMappingInfo mapping) { Set patterns = mapping.getPathPatternsCondition().getPatterns(); 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 f58586fb925c..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the - * new client and its own - * Spring Boot integration. - */ -@Deprecated(since = "3.2.0", forRemoval = true) -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/info/SslInfoContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/SslInfoContributor.java new file mode 100644 index 000000000000..0910d11cdcde --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/SslInfoContributor.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.SslInfoContributor.SslInfoContributorRuntimeHints; +import org.springframework.boot.info.SslInfo; +import org.springframework.context.annotation.ImportRuntimeHints; + +/** + * An {@link InfoContributor} that exposes {@link SslInfo}. + * + * @author Jonatan Ivanov + * @since 3.4.0 + */ +@ImportRuntimeHints(SslInfoContributorRuntimeHints.class) +public class SslInfoContributor implements InfoContributor { + + private final SslInfo sslInfo; + + public SslInfoContributor(SslInfo sslInfo) { + this.sslInfo = sslInfo; + } + + @Override + public void contribute(Builder builder) { + builder.withDetail("ssl", this.sslInfo); + } + + static class SslInfoContributorRuntimeHints implements RuntimeHintsRegistrar { + + private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + this.bindingRegistrar.registerReflectionHints(hints.reflection(), SslInfo.class); + } + + } + +} 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/PrometheusScrapeEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java index 9f0a1db9bd72..2e795433b5b6 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,49 @@ 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} + * @since 3.3.1 + */ + 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/web/jetty/JettyServerThreadPoolMetricsBinder.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettyServerThreadPoolMetricsBinder.java index 2297b4f96ae4..68bb085b6b96 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettyServerThreadPoolMetricsBinder.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettyServerThreadPoolMetricsBinder.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. @@ -46,6 +46,7 @@ public JettyServerThreadPoolMetricsBinder(MeterRegistry meterRegistry, Iterable< } @Override + @SuppressWarnings("resource") protected void bindMetrics(Server server) { ThreadPool threadPool = server.getThreadPool(); if (threadPool != null) { 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..1d65e11f3ca3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/sbom/SbomEndpoint.java @@ -0,0 +1,178 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 { + + static final String APPLICATION_SBOM_ID = "application"; + + private static final List AUTODETECTED_SBOMS = List.of( + new AutodetectedSbom(APPLICATION_SBOM_ID, "classpath:META-INF/sbom/bom.json", true), + new AutodetectedSbom(APPLICATION_SBOM_ID, "classpath:META-INF/sbom/application.cdx.json", true), + new AutodetectedSbom("native-image", "classpath:META-INF/native-image/sbom.json", false)); + + 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 = loadSboms(); + } + + private Map loadSboms() { + Map sboms = new HashMap<>(); + addConfiguredApplicationSbom(sboms); + addAdditionalSboms(sboms); + addAutodetectedSboms(sboms); + return Collections.unmodifiableMap(sboms); + } + + private void addConfiguredApplicationSbom(Map sboms) { + String location = this.properties.getApplication().getLocation(); + if (!StringUtils.hasLength(location)) { + return; + } + Resource resource = loadResource(location); + if (resource != null) { + sboms.put(APPLICATION_SBOM_ID, resource); + } + } + + 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 addAutodetectedSboms(Map sboms) { + for (AutodetectedSbom sbom : AUTODETECTED_SBOMS) { + if (sboms.containsKey(sbom.id())) { + continue; + } + Resource resource = this.resourceLoader.getResource(sbom.resource()); + if (resource.exists()) { + sboms.put(sbom.id(), resource); + } + } + } + + 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)); + } + + @ReadOperation + Sboms sboms() { + return new Sboms(new TreeSet<>(this.sboms.keySet())); + } + + @ReadOperation + Resource sbom(@Selector String id) { + return this.sboms.get(id); + } + + 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()); + } + } + + private record AutodetectedSbom(String id, String resource, boolean needsHints) { + void registerHintsIfNeeded(RuntimeHints hints) { + if (this.needsHints) { + hints.resources().registerPattern(stripClasspathPrefix(this.resource)); + } + } + + private String stripClasspathPrefix(String location) { + return location.substring("classpath:".length()); + } + } + + static class SbomEndpointRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + for (AutodetectedSbom sbom : AUTODETECTED_SBOMS) { + sbom.registerHintsIfNeeded(hints); + } + } + + } + +} 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..666bad4dc82e 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,16 +16,13 @@ package org.springframework.boot.actuate.scheduling; -import java.lang.reflect.Method; import java.time.Duration; +import java.time.Instant; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Function; -import java.util.stream.Collectors; import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.RuntimeHints; @@ -43,16 +40,20 @@ import org.springframework.scheduling.config.ScheduledTask; import org.springframework.scheduling.config.ScheduledTaskHolder; import org.springframework.scheduling.config.Task; +import org.springframework.scheduling.config.TaskExecutionOutcome; +import org.springframework.scheduling.config.TaskExecutionOutcome.Status; import org.springframework.scheduling.config.TriggerTask; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.scheduling.support.ScheduledMethodRunnable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * {@link Endpoint @Endpoint} to expose information about an application's scheduled * tasks. * * @author Andy Wilkinson + * @author Brian Clozel * @since 2.0.0 */ @Endpoint(id = "scheduledtasks") @@ -67,12 +68,16 @@ public ScheduledTasksEndpoint(Collection scheduledTaskHolde @ReadOperation public ScheduledTasksDescriptor scheduledTasks() { - Map> descriptionsByType = this.scheduledTaskHolders.stream() - .flatMap((holder) -> holder.getScheduledTasks().stream()) - .map(ScheduledTask::getTask) - .map(TaskDescriptor::of) - .filter(Objects::nonNull) - .collect(Collectors.groupingBy(TaskDescriptor::getType)); + MultiValueMap descriptionsByType = new LinkedMultiValueMap<>(); + for (ScheduledTaskHolder holder : this.scheduledTaskHolders) { + for (ScheduledTask scheduledTask : holder.getScheduledTasks()) { + TaskType taskType = TaskType.forTask(scheduledTask); + if (taskType != null) { + TaskDescriptor descriptor = taskType.createDescriptor(scheduledTask); + descriptionsByType.add(descriptor.getType(), descriptor); + } + } + } return new ScheduledTasksDescriptor(descriptionsByType); } @@ -119,53 +124,148 @@ public List getCustom() { */ public abstract static class TaskDescriptor { - private static final Map, Function> DESCRIBERS = new LinkedHashMap<>(); + private final TaskType type; + + private final ScheduledTask scheduledTask; - static { - DESCRIBERS.put(FixedRateTask.class, (task) -> new FixedRateTaskDescriptor((FixedRateTask) task)); - DESCRIBERS.put(FixedDelayTask.class, (task) -> new FixedDelayTaskDescriptor((FixedDelayTask) task)); - DESCRIBERS.put(CronTask.class, (task) -> new CronTaskDescriptor((CronTask) task)); - DESCRIBERS.put(TriggerTask.class, (task) -> describeTriggerTask((TriggerTask) task)); + private final RunnableDescriptor runnable; + + protected TaskDescriptor(ScheduledTask scheduledTask, TaskType type) { + this.scheduledTask = scheduledTask; + this.type = type; + this.runnable = new RunnableDescriptor(scheduledTask.getTask().getRunnable()); } - private final TaskType type; + private TaskType getType() { + return this.type; + } - private final RunnableDescriptor runnable; + public final RunnableDescriptor getRunnable() { + return this.runnable; + } - private static TaskDescriptor of(Task task) { - return DESCRIBERS.entrySet() - .stream() - .filter((entry) -> entry.getKey().isInstance(task)) - .map((entry) -> entry.getValue().apply(task)) - .findFirst() - .orElse(null); + public final NextExecution getNextExecution() { + Instant nextExecution = this.scheduledTask.nextExecution(); + if (nextExecution != null) { + return new NextExecution(nextExecution); + } + return null; } - private static TaskDescriptor describeTriggerTask(TriggerTask triggerTask) { - Trigger trigger = triggerTask.getTrigger(); - if (trigger instanceof CronTrigger cronTrigger) { - return new CronTaskDescriptor(triggerTask, cronTrigger); + public final LastExecution getLastExecution() { + TaskExecutionOutcome lastExecutionOutcome = this.scheduledTask.getTask().getLastExecutionOutcome(); + if (lastExecutionOutcome.status() != Status.NONE) { + return new LastExecution(lastExecutionOutcome); } - if (trigger instanceof PeriodicTrigger periodicTrigger) { - if (periodicTrigger.isFixedRate()) { - return new FixedRateTaskDescriptor(triggerTask, periodicTrigger); - } - return new FixedDelayTaskDescriptor(triggerTask, periodicTrigger); + return null; + } + + } + + public static final class NextExecution { + + private final Instant time; + + public NextExecution(Instant time) { + this.time = time; + } + + public Instant getTime() { + return this.time; + } + + } + + public static final class LastExecution { + + private final TaskExecutionOutcome lastExecutionOutcome; + + private LastExecution(TaskExecutionOutcome lastExecutionOutcome) { + this.lastExecutionOutcome = lastExecutionOutcome; + } + + public Status getStatus() { + return this.lastExecutionOutcome.status(); + } + + public Instant getTime() { + return this.lastExecutionOutcome.executionTime(); + } + + public ExceptionInfo getException() { + Throwable throwable = this.lastExecutionOutcome.throwable(); + if (throwable != null) { + return new ExceptionInfo(throwable); } - return new CustomTriggerTaskDescriptor(triggerTask); + return null; } - protected TaskDescriptor(TaskType type, Runnable runnable) { - this.type = type; - this.runnable = new RunnableDescriptor(runnable); + } + + public static final class ExceptionInfo { + + private final Throwable throwable; + + private ExceptionInfo(Throwable throwable) { + this.throwable = throwable; } - private TaskType getType() { - return this.type; + public String getType() { + return this.throwable.getClass().getName(); } - public final RunnableDescriptor getRunnable() { - return this.runnable; + public String getMessage() { + return this.throwable.getMessage(); + } + + } + + private enum TaskType { + + CRON(CronTask.class, + (scheduledTask) -> new CronTaskDescriptor(scheduledTask, (CronTask) scheduledTask.getTask())), + FIXED_DELAY(FixedDelayTask.class, + (scheduledTask) -> new FixedDelayTaskDescriptor(scheduledTask, + (FixedDelayTask) scheduledTask.getTask())), + FIXED_RATE(FixedRateTask.class, + (scheduledTask) -> new FixedRateTaskDescriptor(scheduledTask, (FixedRateTask) scheduledTask.getTask())), + CUSTOM_TRIGGER(TriggerTask.class, TaskType::describeTriggerTask); + + final Class taskClass; + + final Function describer; + + TaskType(Class taskClass, Function describer) { + this.taskClass = taskClass; + this.describer = describer; + } + + static TaskType forTask(ScheduledTask scheduledTask) { + for (TaskType taskType : TaskType.values()) { + if (taskType.taskClass.isInstance(scheduledTask.getTask())) { + return taskType; + } + } + return null; + } + + TaskDescriptor createDescriptor(ScheduledTask scheduledTask) { + return this.describer.apply(scheduledTask); + } + + private static TaskDescriptor describeTriggerTask(ScheduledTask scheduledTask) { + TriggerTask triggerTask = (TriggerTask) scheduledTask.getTask(); + Trigger trigger = triggerTask.getTrigger(); + if (trigger instanceof CronTrigger cronTrigger) { + return new CronTaskDescriptor(scheduledTask, triggerTask, cronTrigger); + } + if (trigger instanceof PeriodicTrigger periodicTrigger) { + if (periodicTrigger.isFixedRate()) { + return new FixedRateTaskDescriptor(scheduledTask, triggerTask, periodicTrigger); + } + return new FixedDelayTaskDescriptor(scheduledTask, triggerTask, periodicTrigger); + } + return new CustomTriggerTaskDescriptor(scheduledTask); } } @@ -179,14 +279,15 @@ public static class IntervalTaskDescriptor extends TaskDescriptor { private final long interval; - protected IntervalTaskDescriptor(TaskType type, IntervalTask task) { - super(type, task.getRunnable()); - this.initialDelay = task.getInitialDelayDuration().toMillis(); - this.interval = task.getIntervalDuration().toMillis(); + protected IntervalTaskDescriptor(ScheduledTask scheduledTask, TaskType type, IntervalTask intervalTask) { + super(scheduledTask, type); + this.initialDelay = intervalTask.getInitialDelayDuration().toMillis(); + this.interval = intervalTask.getIntervalDuration().toMillis(); } - protected IntervalTaskDescriptor(TaskType type, TriggerTask task, PeriodicTrigger trigger) { - super(type, task.getRunnable()); + protected IntervalTaskDescriptor(ScheduledTask scheduledTask, TaskType type, TriggerTask task, + PeriodicTrigger trigger) { + super(scheduledTask, type); Duration initialDelayDuration = trigger.getInitialDelayDuration(); this.initialDelay = (initialDelayDuration != null) ? initialDelayDuration.toMillis() : 0; this.interval = trigger.getPeriodDuration().toMillis(); @@ -208,12 +309,12 @@ public long getInterval() { */ public static final class FixedDelayTaskDescriptor extends IntervalTaskDescriptor { - private FixedDelayTaskDescriptor(FixedDelayTask task) { - super(TaskType.FIXED_DELAY, task); + private FixedDelayTaskDescriptor(ScheduledTask scheduledTask, FixedDelayTask task) { + super(scheduledTask, TaskType.FIXED_DELAY, task); } - private FixedDelayTaskDescriptor(TriggerTask task, PeriodicTrigger trigger) { - super(TaskType.FIXED_DELAY, task, trigger); + private FixedDelayTaskDescriptor(ScheduledTask scheduledTask, TriggerTask task, PeriodicTrigger trigger) { + super(scheduledTask, TaskType.FIXED_DELAY, task, trigger); } } @@ -224,12 +325,12 @@ private FixedDelayTaskDescriptor(TriggerTask task, PeriodicTrigger trigger) { */ public static final class FixedRateTaskDescriptor extends IntervalTaskDescriptor { - private FixedRateTaskDescriptor(FixedRateTask task) { - super(TaskType.FIXED_RATE, task); + private FixedRateTaskDescriptor(ScheduledTask scheduledTask, FixedRateTask task) { + super(scheduledTask, TaskType.FIXED_RATE, task); } - private FixedRateTaskDescriptor(TriggerTask task, PeriodicTrigger trigger) { - super(TaskType.FIXED_RATE, task, trigger); + private FixedRateTaskDescriptor(ScheduledTask scheduledTask, TriggerTask task, PeriodicTrigger trigger) { + super(scheduledTask, TaskType.FIXED_RATE, task, trigger); } } @@ -242,13 +343,13 @@ public static final class CronTaskDescriptor extends TaskDescriptor { private final String expression; - private CronTaskDescriptor(CronTask task) { - super(TaskType.CRON, task.getRunnable()); - this.expression = task.getExpression(); + private CronTaskDescriptor(ScheduledTask scheduledTask, CronTask cronTask) { + super(scheduledTask, TaskType.CRON); + this.expression = cronTask.getExpression(); } - private CronTaskDescriptor(TriggerTask task, CronTrigger trigger) { - super(TaskType.CRON, task.getRunnable()); + private CronTaskDescriptor(ScheduledTask scheduledTask, TriggerTask triggerTask, CronTrigger trigger) { + super(scheduledTask, TaskType.CRON); this.expression = trigger.getExpression(); } @@ -265,9 +366,10 @@ public static final class CustomTriggerTaskDescriptor extends TaskDescriptor { private final String trigger; - private CustomTriggerTaskDescriptor(TriggerTask task) { - super(TaskType.CUSTOM_TRIGGER, task.getRunnable()); - this.trigger = task.getTrigger().toString(); + private CustomTriggerTaskDescriptor(ScheduledTask scheduledTask) { + super(scheduledTask, TaskType.CUSTOM_TRIGGER); + TriggerTask triggerTask = (TriggerTask) scheduledTask.getTask(); + this.trigger = triggerTask.getTrigger().toString(); } public String getTrigger() { @@ -284,13 +386,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() { @@ -299,12 +395,6 @@ public String getTarget() { } - private enum TaskType { - - CRON, CUSTOM_TRIGGER, FIXED_DELAY, FIXED_RATE - - } - static class ScheduledTasksEndpointRuntimeHints implements RuntimeHintsRegistrar { private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); 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/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java new file mode 100644 index 000000000000..18d8f2e19320 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.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.ssl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.info.SslInfo; +import org.springframework.boot.info.SslInfo.BundleInfo; +import org.springframework.boot.info.SslInfo.CertificateChainInfo; +import org.springframework.boot.info.SslInfo.CertificateInfo; + +/** + * {@link HealthIndicator} that checks the certificates the application uses and reports + * {@link Status#OUT_OF_SERVICE} when a certificate is invalid. + * + * @author Jonatan Ivanov + * @since 3.4.0 + */ +public class SslHealthIndicator extends AbstractHealthIndicator { + + private final SslInfo sslInfo; + + public SslHealthIndicator(SslInfo sslInfo) { + this.sslInfo = sslInfo; + } + + @Override + protected void doHealthCheck(Builder builder) throws Exception { + List validCertificateChains = new ArrayList<>(); + List invalidCertificateChains = new ArrayList<>(); + for (BundleInfo bundle : this.sslInfo.getBundles()) { + for (CertificateChainInfo certificateChain : bundle.getCertificateChains()) { + if (containsOnlyValidCertificates(certificateChain)) { + validCertificateChains.add(certificateChain); + } + else if (containsInvalidCertificate(certificateChain)) { + invalidCertificateChains.add(certificateChain); + } + } + } + builder.status((invalidCertificateChains.isEmpty()) ? Status.UP : Status.OUT_OF_SERVICE); + builder.withDetail("validChains", validCertificateChains); + builder.withDetail("invalidChains", invalidCertificateChains); + } + + private boolean containsOnlyValidCertificates(CertificateChainInfo certificateChain) { + return validatableCertificates(certificateChain).allMatch(this::isValidCertificate); + } + + private boolean containsInvalidCertificate(CertificateChainInfo certificateChain) { + return validatableCertificates(certificateChain).anyMatch(this::isNotValidCertificate); + } + + private Stream validatableCertificates(CertificateChainInfo certificateChain) { + return certificateChain.getCertificates().stream().filter((certificate) -> certificate.getValidity() != null); + } + + private boolean isValidCertificate(CertificateInfo certificate) { + return certificate.getValidity().getStatus().isValid(); + } + + private boolean isNotValidCertificate(CertificateInfo certificate) { + return !isValidCertificate(certificate); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/package-info.java new file mode 100644 index 000000000000..a4296abf5e29 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/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 ssl concerns. + */ +package org.springframework.boot.actuate.ssl; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AccessTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AccessTests.java new file mode 100644 index 000000000000..a313870b19d2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AccessTests.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.actuate.endpoint; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Access}. + * + * @author Phillip Webb + */ +class AccessTests { + + @Test + void capWhenAboveMaximum() { + assertThat(Access.UNRESTRICTED.cap(Access.READ_ONLY)).isEqualTo(Access.READ_ONLY); + } + + @Test + void capWhenAtMaximum() { + assertThat(Access.READ_ONLY.cap(Access.READ_ONLY)).isEqualTo(Access.READ_ONLY); + } + + @Test + void capWhenBelowMaximum() { + assertThat(Access.NONE.cap(Access.READ_ONLY)).isEqualTo(Access.NONE); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/OperationFilterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/OperationFilterTests.java new file mode 100644 index 000000000000..bf2276665f53 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/OperationFilterTests.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.endpoint; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OperationFilter}. + * + * @author Andy Wilkinson + */ +class OperationFilterTests { + + private final EndpointAccessResolver accessResolver = mock(EndpointAccessResolver.class); + + private final Operation operation = mock(Operation.class); + + private final OperationFilter filter = OperationFilter.byAccess(this.accessResolver); + + @Test + void whenAccessIsUnrestrictedThenMatchReturnsTrue() { + EndpointId endpointId = EndpointId.of("test"); + Access defaultAccess = Access.READ_ONLY; + given(this.accessResolver.accessFor(endpointId, defaultAccess)).willReturn(Access.UNRESTRICTED); + assertThat(this.filter.match(this.operation, endpointId, defaultAccess)).isTrue(); + } + + @Test + void whenAccessIsNoneThenMatchReturnsFalse() { + EndpointId endpointId = EndpointId.of("test"); + Access defaultAccess = Access.READ_ONLY; + given(this.accessResolver.accessFor(endpointId, defaultAccess)).willReturn(Access.NONE); + assertThat(this.filter.match(this.operation, endpointId, defaultAccess)).isFalse(); + } + + @Test + void whenAccessIsReadOnlyAndOperationTypeIsReadThenMatchReturnsTrue() { + EndpointId endpointId = EndpointId.of("test"); + Access defaultAccess = Access.READ_ONLY; + given(this.accessResolver.accessFor(endpointId, defaultAccess)).willReturn(Access.READ_ONLY); + given(this.operation.getType()).willReturn(OperationType.READ); + assertThat(this.filter.match(this.operation, endpointId, defaultAccess)).isTrue(); + } + + @Test + void whenAccessIsReadOnlyAndOperationTypeIsWriteThenMatchReturnsFalse() { + EndpointId endpointId = EndpointId.of("test"); + Access defaultAccess = Access.READ_ONLY; + given(this.accessResolver.accessFor(endpointId, defaultAccess)).willReturn(Access.READ_ONLY); + given(this.operation.getType()).willReturn(OperationType.WRITE); + assertThat(this.filter.match(this.operation, endpointId, defaultAccess)).isFalse(); + } + + @Test + void whenAccessIsReadOnlyAndOperationTypeIsDeleteThenMatchReturnsFalse() { + EndpointId endpointId = EndpointId.of("test"); + Access defaultAccess = Access.READ_ONLY; + given(this.accessResolver.accessFor(endpointId, defaultAccess)).willReturn(Access.READ_ONLY); + given(this.operation.getType()).willReturn(OperationType.DELETE); + assertThat(this.filter.match(this.operation, endpointId, defaultAccess)).isFalse(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscovererEndpointFilterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscovererEndpointFilterTests.java index c619700eab69..b457b2fa951d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscovererEndpointFilterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscovererEndpointFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.actuate.endpoint.annotation; import java.util.Collection; +import java.util.Collections; import org.junit.jupiter.api.Test; @@ -79,7 +80,7 @@ abstract static class TestDiscovererA extends EndpointDiscoverer invokerAdvisors, Collection>> filters) { - super(applicationContext, parameterValueMapper, invokerAdvisors, filters); + super(applicationContext, parameterValueMapper, invokerAdvisors, filters, Collections.emptyList()); } } @@ -89,7 +90,7 @@ abstract static class TestDiscovererB extends EndpointDiscoverer invokerAdvisors, Collection>> filters) { - super(applicationContext, parameterValueMapper, invokerAdvisors, filters); + super(applicationContext, parameterValueMapper, invokerAdvisors, filters, Collections.emptyList()); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java index 96b68a2a56a3..131e0e76ef72 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,13 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; +import org.springframework.boot.actuate.endpoint.OperationFilter; +import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -251,19 +254,33 @@ void getEndpointsWhenHasProxiedEndpointShouldReturnEndpoint() { } @Test - void getEndpointsShouldApplyFilters() { + void getEndpointsShouldApplyEndpointFilters() { load(SpecializedEndpointsConfiguration.class, (context) -> { EndpointFilter filter = (endpoint) -> { EndpointId id = endpoint.getEndpointId(); return !id.equals(EndpointId.of("specialized")) && !id.equals(EndpointId.of("specialized-superclass")); }; SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(context, - Collections.singleton(filter)); + Collections.singleton(filter), Collections.emptyList()); Map endpoints = mapEndpoints(discoverer.getEndpoints()); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); }); } + @Test + void getEndpointsShouldApplyOperationFilters() { + load(SpecializedEndpointsConfiguration.class, (context) -> { + OperationFilter operationFilter = (operation, endpointId, + defaultAccess) -> operation.getType() == OperationType.READ; + SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(context, + Collections.emptyList(), List.of(operationFilter)); + Map endpoints = mapEndpoints(discoverer.getEndpoints()); + assertThat(endpoints.values()) + .allSatisfy((endpoint) -> assertThat(endpoint.getOperations()).extracting(SpecializedOperation::getType) + .containsOnly(OperationType.READ)); + }); + } + private void hasTestEndpoint(AnnotationConfigApplicationContext context) { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); Map endpoints = mapEndpoints(discoverer.getEndpoints()); @@ -536,10 +553,17 @@ static class TestEndpointDiscoverer extends EndpointDiscoverer invokerAdvisors, Collection> filters) { - super(applicationContext, parameterValueMapper, invokerAdvisors, filters); + super(applicationContext, parameterValueMapper, invokerAdvisors, filters, Collections.emptyList()); + } + + @Override + protected TestExposableEndpoint createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, + Collection operations) { + return new TestExposableEndpoint(this, endpointBean, id, defaultAccess, operations); } @Override + @SuppressWarnings("removal") protected TestExposableEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { return new TestExposableEndpoint(this, endpointBean, id, enabledByDefault, operations); @@ -563,15 +587,24 @@ static class SpecializedEndpointDiscoverer extends EndpointDiscoverer { SpecializedEndpointDiscoverer(ApplicationContext applicationContext) { - this(applicationContext, Collections.emptyList()); + this(applicationContext, Collections.emptyList(), Collections.emptyList()); } SpecializedEndpointDiscoverer(ApplicationContext applicationContext, - Collection> filters) { - super(applicationContext, new ConversionServiceParameterValueMapper(), Collections.emptyList(), filters); + Collection> filters, + Collection> operationFilters) { + super(applicationContext, new ConversionServiceParameterValueMapper(), Collections.emptyList(), filters, + operationFilters); } @Override + protected SpecializedExposableEndpoint createEndpoint(Object endpointBean, EndpointId id, Access defaultAccess, + Collection operations) { + return new SpecializedExposableEndpoint(this, endpointBean, id, defaultAccess, operations); + } + + @Override + @SuppressWarnings("removal") protected SpecializedExposableEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { return new SpecializedExposableEndpoint(this, endpointBean, id, enabledByDefault, operations); @@ -593,6 +626,12 @@ protected OperationKey createOperationKey(SpecializedOperation operation) { static class TestExposableEndpoint extends AbstractDiscoveredEndpoint { + TestExposableEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, + Access defaultAccess, Collection operations) { + super(discoverer, endpointBean, id, defaultAccess, operations); + } + + @SuppressWarnings("removal") TestExposableEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { super(discoverer, endpointBean, id, enabledByDefault, operations); @@ -602,6 +641,13 @@ static class TestExposableEndpoint extends AbstractDiscoveredEndpoint { + @SuppressWarnings("removal") + SpecializedExposableEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, + Access defaultAccess, Collection operations) { + super(discoverer, endpointBean, id, defaultAccess, operations); + } + + @SuppressWarnings("removal") SpecializedExposableEndpoint(EndpointDiscoverer discoverer, Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { super(discoverer, endpointBean, id, enabledByDefault, operations); 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/jmx/JmxEndpointExporterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java index 37a397876150..cfd0a8d21622 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,13 @@ void registerWhenRegistrationFailsShouldThrowException() throws Exception { .withMessageContaining("Failed to register MBean for endpoint 'test"); } + @Test + void registerWhenEndpointHasNoOperationsShouldNotCreateMBean() { + this.endpoints.add(new TestExposableJmxEndpoint()); + this.exporter.afterPropertiesSet(); + then(this.mBeanServer).shouldHaveNoInteractions(); + } + @Test void destroyShouldUnregisterMBeans() throws Exception { this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation())); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestExposableJmxEndpoint.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestExposableJmxEndpoint.java index 33c04a901328..3d78ebb9c473 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestExposableJmxEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestExposableJmxEndpoint.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,6 +19,7 @@ import java.util.Arrays; import java.util.Collection; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; /** @@ -44,6 +45,7 @@ public EndpointId getEndpointId() { } @Override + @SuppressWarnings("removal") public boolean isEnableByDefault() { return true; } @@ -53,4 +55,9 @@ public Collection getOperations() { return this.operations; } + @Override + public Access getDefaultAccess() { + return Access.UNRESTRICTED; + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java index fc4465f3987f..75261ddb29f2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -280,7 +280,8 @@ private void load(Class configuration, Function timeToLive, ConversionServiceParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper( DefaultConversionService.getSharedInstance()); JmxEndpointDiscoverer discoverer = new JmxEndpointDiscoverer(context, parameterMapper, - Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), Collections.emptyList()); + Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), Collections.emptyList(), + Collections.emptyList()); consumer.accept(discoverer); } } 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..d48488aef00a 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. @@ -37,6 +37,7 @@ * * @author Andy Wilkinson */ +@SuppressWarnings("removal") class EndpointLinksResolverTests { @Test @@ -75,6 +76,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..1a1d8cf247bb 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({ "deprecation", "removal" }) class EndpointServletTests { @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java index 37e3596fdfb0..1d5b2b466c15 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java @@ -129,19 +129,36 @@ void getEndpointWhenMissingIdShouldReturnNull() { assertThat(mapped.getEndpoint(EndpointId.of("xx"))).isNull(); } + @Test + void getAdditionalPathsShouldReturnCanonicalAdditionalPaths() { + PathMappedEndpoints mapped = createTestMapped(null); + assertThat(mapped.getAdditionalPaths(WebServerNamespace.SERVER, EndpointId.of("e2"))).containsExactly("/a2", + "/A2"); + assertThat(mapped.getAdditionalPaths(WebServerNamespace.MANAGEMENT, EndpointId.of("e2"))).isEmpty(); + assertThat(mapped.getAdditionalPaths(WebServerNamespace.SERVER, EndpointId.of("e3"))).isEmpty(); + } + private PathMappedEndpoints createTestMapped(String basePath) { List> endpoints = new ArrayList<>(); endpoints.add(mockEndpoint(EndpointId.of("e1"))); - endpoints.add(mockEndpoint(EndpointId.of("e2"), "p2")); + endpoints.add(mockEndpoint(EndpointId.of("e2"), "p2", WebServerNamespace.SERVER, List.of("/a2", "A2"))); endpoints.add(mockEndpoint(EndpointId.of("e3"), "p3")); endpoints.add(mockEndpoint(EndpointId.of("e4"))); return new PathMappedEndpoints(basePath, () -> endpoints); } private TestPathMappedEndpoint mockEndpoint(EndpointId id, String rootPath) { + return mockEndpoint(id, rootPath, null, null); + } + + private TestPathMappedEndpoint mockEndpoint(EndpointId id, String rootPath, WebServerNamespace webServerNamespace, + List additionalPaths) { TestPathMappedEndpoint endpoint = mock(TestPathMappedEndpoint.class); given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getRootPath()).willReturn(rootPath); + if (webServerNamespace != null && additionalPaths != null) { + given(endpoint.getAdditionalPaths(webServerNamespace)).willReturn(additionalPaths); + } return endpoint; } 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..6e9c9dacae1b 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. @@ -17,12 +17,16 @@ package org.springframework.boot.actuate.endpoint.web; import java.util.Collections; +import java.util.EnumSet; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterRegistration; import jakarta.servlet.GenericServlet; import jakarta.servlet.Servlet; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRegistration.Dynamic; +import jakarta.servlet.ServletRegistration; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.junit.jupiter.api.Test; @@ -30,6 +34,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import static org.assertj.core.api.Assertions.assertThat; @@ -48,13 +53,17 @@ * @author Stephane Nicoll */ @ExtendWith(MockitoExtension.class) +@SuppressWarnings({ "deprecation", "removal" }) class ServletEndpointRegistrarTests { @Mock private ServletContext servletContext; @Mock - private Dynamic dynamic; + private ServletRegistration.Dynamic servletDynamic; + + @Mock + private FilterRegistration.Dynamic filterDynamic; @Test void createWhenServletEndpointsIsNullShouldThrowException() { @@ -83,42 +92,77 @@ void onStartupWhenHasRootBasePathShouldNotAddDuplicateSlash() throws ServletExce } private void assertBasePath(String basePath, String expectedMapping) throws ServletException { - given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.dynamic); + given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.servletDynamic); ExposableServletEndpoint endpoint = mockEndpoint(new EndpointServlet(TestServlet.class)); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(basePath, Collections.singleton(endpoint)); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(basePath, Collections.singleton(endpoint), + (endpointId, defaultAccess) -> Access.UNRESTRICTED); registrar.onStartup(this.servletContext); then(this.servletContext).should() .addServlet(eq("test-actuator-endpoint"), (Servlet) assertArg((servlet) -> assertThat(servlet).isInstanceOf(TestServlet.class))); - then(this.dynamic).should().addMapping(expectedMapping); + then(this.servletDynamic).should().addMapping(expectedMapping); + then(this.servletContext).shouldHaveNoMoreInteractions(); } @Test void onStartupWhenHasInitParametersShouldRegisterInitParameters() throws Exception { - given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.dynamic); + given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.servletDynamic); ExposableServletEndpoint endpoint = mockEndpoint( new EndpointServlet(TestServlet.class).withInitParameter("a", "b")); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint)); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint), + (endpointId, defaultAccess) -> Access.UNRESTRICTED); registrar.onStartup(this.servletContext); - then(this.dynamic).should().setInitParameters(Collections.singletonMap("a", "b")); + then(this.servletDynamic).should().setInitParameters(Collections.singletonMap("a", "b")); } @Test void onStartupWhenHasLoadOnStartupShouldRegisterLoadOnStartup() throws Exception { - given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.dynamic); + given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.servletDynamic); ExposableServletEndpoint endpoint = mockEndpoint(new EndpointServlet(TestServlet.class).withLoadOnStartup(7)); - ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint)); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint), + (endpointId, defaultAccess) -> Access.UNRESTRICTED); registrar.onStartup(this.servletContext); - then(this.dynamic).should().setLoadOnStartup(7); + then(this.servletDynamic).should().setLoadOnStartup(7); } @Test void onStartupWhenHasNotLoadOnStartupShouldRegisterDefaultValue() throws Exception { - given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.dynamic); + given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.servletDynamic); ExposableServletEndpoint endpoint = mockEndpoint(new EndpointServlet(TestServlet.class)); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint), + (endpointId, defaultAccess) -> Access.UNRESTRICTED); + registrar.onStartup(this.servletContext); + then(this.servletDynamic).should().setLoadOnStartup(-1); + } + + @Test + void onStartupWhenAccessIsDisabledShouldNotRegister() throws Exception { + ExposableServletEndpoint endpoint = mock(ExposableServletEndpoint.class); + given(endpoint.getEndpointId()).willReturn(EndpointId.of("test")); ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint)); registrar.onStartup(this.servletContext); - then(this.dynamic).should().setLoadOnStartup(-1); + then(this.servletContext).shouldHaveNoInteractions(); + } + + @Test + void onStartupWhenAccessIsReadOnlyShouldRegisterServletWithFilter() throws Exception { + ExposableServletEndpoint endpoint = mockEndpoint(new EndpointServlet(TestServlet.class)); + given(endpoint.getEndpointId()).willReturn(EndpointId.of("test")); + given(this.servletContext.addServlet(any(String.class), any(Servlet.class))).willReturn(this.servletDynamic); + given(this.servletContext.addFilter(any(String.class), any(Filter.class))).willReturn(this.filterDynamic); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", Collections.singleton(endpoint), + (endpointId, defaultAccess) -> Access.READ_ONLY); + registrar.onStartup(this.servletContext); + then(this.servletContext).should() + .addServlet(eq("test-actuator-endpoint"), + (Servlet) assertArg((servlet) -> assertThat(servlet).isInstanceOf(TestServlet.class))); + then(this.servletDynamic).should().addMapping("/actuator/test/*"); + then(this.servletContext).should() + .addFilter(eq("test-actuator-endpoint-access-filter"), (Filter) assertArg((filter) -> assertThat(filter) + .isInstanceOf( + org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar.ReadOnlyAccessFilter.class))); + then(this.filterDynamic).should() + .addMappingForServletNames(EnumSet.allOf(DispatcherType.class), false, "test-actuator-endpoint"); } private ExposableServletEndpoint mockEndpoint(EndpointServlet endpointServlet) { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java index 91eaebb7f0e9..add2c43fc2e3 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.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. @@ -53,4 +53,9 @@ void namespaceWithDifferentValuesAreNotEqual() { assertThat(WebServerNamespace.from("value")).isNotEqualTo(WebServerNamespace.from("other")); } + @Test + void toStringReturnsString() { + assertThat(WebServerNamespace.from("value")).hasToString("value"); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java index 6645dc5b64a9..f02671d1ab9b 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java @@ -68,7 +68,8 @@ WebEndpointDiscoverer webEndpointDiscoverer(EndpointMediaTypes endpointMediaType ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper( DefaultConversionService.getSharedInstance()); return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, - pathMappers.orderedStream().toList(), Collections.emptyList(), Collections.emptyList()); + pathMappers.orderedStream().toList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList()); } @Bean 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..a961725e4f98 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({ "deprecation", "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..dbfd77e6c154 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({ "deprecation", "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/annotation/WebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java index 02cc46336e3a..a6656b3862d0 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,12 +43,14 @@ import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvoker; import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer.WebEndpointDiscovererRuntimeHints; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -223,6 +225,23 @@ void getEndpointsWhenHasCustomPathShouldReturnCustomPath() { }); } + @Test + void getEndpointsWhenHasAdditionalPaths() { + AdditionalPathsMapper additionalPathsMapper = (id, webServerNamespace) -> { + if (!WebServerNamespace.SERVER.equals(webServerNamespace)) { + return Collections.emptyList(); + } + return List.of("/test"); + }; + load((id) -> null, EndpointId::toString, additionalPathsMapper, + AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> { + Map endpoints = mapEndpoints(discoverer.getEndpoints()); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test")); + assertThat(endpoint.getAdditionalPaths(WebServerNamespace.SERVER)).containsExactly("/test"); + assertThat(endpoint.getAdditionalPaths(WebServerNamespace.MANAGEMENT)).isEmpty(); + }); + } + @Test void shouldRegisterHints() { RuntimeHints runtimeHints = new RuntimeHints(); @@ -230,7 +249,6 @@ void shouldRegisterHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(WebEndpointFilter.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints); - } private void load(Class configuration, Consumer consumer) { @@ -239,6 +257,12 @@ private void load(Class configuration, Consumer consum private void load(Function timeToLive, PathMapper endpointPathMapper, Class configuration, Consumer consumer) { + load(timeToLive, endpointPathMapper, null, configuration, consumer); + } + + private void load(Function timeToLive, PathMapper endpointPathMapper, + AdditionalPathsMapper additionalPathsMapper, Class configuration, + Consumer consumer) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configuration)) { ConversionServiceParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper( DefaultConversionService.getSharedInstance()); @@ -246,7 +270,9 @@ private void load(Function timeToLive, PathMapper endpointPath Collections.singletonList("application/json")); WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(context, parameterMapper, mediaTypes, Collections.singletonList(endpointPathMapper), - Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), Collections.emptyList()); + (additionalPathsMapper != null) ? Collections.singletonList(additionalPathsMapper) : null, + Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), Collections.emptyList(), + Collections.emptyList()); consumer.accept(discoverer); } } 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..6fc0654e348a 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. @@ -24,6 +24,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; @@ -40,6 +42,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -49,6 +52,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -57,7 +61,10 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@SuppressWarnings("removal") +@Deprecated(since = "3.3.5", forRemoval = true) class ControllerEndpointHandlerMappingIntegrationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner( @@ -65,7 +72,7 @@ class ControllerEndpointHandlerMappingIntegrationTests { .withUserConfiguration(EndpointConfiguration.class, ExampleWebFluxEndpoint.class); @Test - void get() { + void getMapping() { this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.get() .uri("/actuator/example/one") .accept(MediaType.TEXT_PLAIN) @@ -89,7 +96,7 @@ void getWithUnacceptableContentType() { } @Test - void post() { + void postMapping() { this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.post() .uri("/actuator/example/two") .bodyValue(Collections.singletonMap("id", "test")) @@ -100,6 +107,71 @@ void post() { .valueEquals(HttpHeaders.LOCATION, "/example/test"))); } + @Test + void postMappingWithReadOnlyAccessRespondsWith404() { + this.contextRunner.withPropertyValues("endpoint-access=READ_ONLY") + .run(withWebTestClient((webTestClient) -> webTestClient.post() + .uri("/actuator/example/two") + .bodyValue(Collections.singletonMap("id", "test")) + .exchange() + .expectStatus() + .isNotFound())); + } + + @Test + void getToRequestMapping() { + this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.get() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class) + .isEqualTo("Three"))); + } + + @Test + void getToRequestMappingWithReadOnlyAccess() { + this.contextRunner.withPropertyValues("endpoint-access=READ_ONLY") + .run(withWebTestClient((webTestClient) -> webTestClient.get() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class) + .isEqualTo("Three"))); + } + + @Test + void postToRequestMapping() { + this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.post() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class) + .isEqualTo("Three"))); + } + + @Test + void postToRequestMappingWithReadOnlyAccessRespondsWith405() { + this.contextRunner.withPropertyValues("endpoint-access=READ_ONLY") + .run(withWebTestClient((webTestClient) -> webTestClient.post() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.METHOD_NOT_ALLOWED))); + } + private ContextConsumer withWebTestClient( Consumer webClient) { return (context) -> { @@ -141,9 +213,15 @@ ControllerEndpointDiscoverer webEndpointDiscoverer(ApplicationContext applicatio } @Bean - ControllerEndpointHandlerMapping webEndpointHandlerMapping(ControllerEndpointsSupplier endpointsSupplier) { + ControllerEndpointHandlerMapping webEndpointHandlerMapping(ControllerEndpointsSupplier endpointsSupplier, + EndpointAccessResolver endpointAccessResolver) { return new ControllerEndpointHandlerMapping(new EndpointMapping("actuator"), - endpointsSupplier.getEndpoints(), null); + endpointsSupplier.getEndpoints(), null, endpointAccessResolver); + } + + @Bean + EndpointAccessResolver endpointAccessResolver(Environment environment) { + return (id, defaultAccess) -> environment.getProperty("endpoint-access", Access.class, Access.UNRESTRICTED); } } @@ -161,6 +239,11 @@ ResponseEntity two(@RequestBody Map content) { return ResponseEntity.created(URI.create("/example/" + content.get("id"))).build(); } + @RequestMapping(path = "/three", produces = MediaType.TEXT_PLAIN_VALUE) + String three() { + return "Three"; + } + } } 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..51e9c83c2bce 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. @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; @@ -45,7 +46,10 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.5", forRemoval = true) +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingTests { private final StaticApplicationContext context = new StaticApplicationContext(); @@ -98,7 +102,7 @@ private Object getHandler(ControllerEndpointHandlerMapping mapping, HttpMethod m private ControllerEndpointHandlerMapping createMapping(String prefix, ExposableControllerEndpoint... endpoints) { ControllerEndpointHandlerMapping mapping = new ControllerEndpointHandlerMapping(new EndpointMapping(prefix), - Arrays.asList(endpoints), null); + Arrays.asList(endpoints), null, (endpointId, defaultAccess) -> Access.UNRESTRICTED); mapping.setApplicationContext(this.context); mapping.afterPropertiesSet(); return mapping; 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..d9ad24f73a8d 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. @@ -24,6 +24,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.Access; +import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; @@ -41,6 +43,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -49,6 +52,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.util.DefaultUriBuilderFactory; /** @@ -56,7 +60,10 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.5", forRemoval = true) +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingIntegrationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( @@ -64,7 +71,7 @@ class ControllerEndpointHandlerMappingIntegrationTests { .withUserConfiguration(EndpointConfiguration.class, ExampleMvcEndpoint.class); @Test - void get() { + void getMapping() { this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.get() .uri("/actuator/example/one") .accept(MediaType.TEXT_PLAIN) @@ -88,7 +95,7 @@ void getWithUnacceptableContentType() { } @Test - void post() { + void postMapping() { this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.post() .uri("/actuator/example/two") .bodyValue(Collections.singletonMap("id", "test")) @@ -99,6 +106,71 @@ void post() { .valueEquals(HttpHeaders.LOCATION, "/example/test"))); } + @Test + void postMappingWithReadOnlyAccessRespondsWith404() { + this.contextRunner.withPropertyValues("endpoint-access=READ_ONLY") + .run(withWebTestClient((webTestClient) -> webTestClient.post() + .uri("/actuator/example/two") + .bodyValue(Collections.singletonMap("id", "test")) + .exchange() + .expectStatus() + .isNotFound())); + } + + @Test + void getToRequestMapping() { + this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.get() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class) + .isEqualTo("Three"))); + } + + @Test + void getToRequestMappingWithReadOnlyAccess() { + this.contextRunner.withPropertyValues("endpoint-access=READ_ONLY") + .run(withWebTestClient((webTestClient) -> webTestClient.get() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class) + .isEqualTo("Three"))); + } + + @Test + void postToRequestMapping() { + this.contextRunner.run(withWebTestClient((webTestClient) -> webTestClient.post() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentTypeCompatibleWith(MediaType.TEXT_PLAIN) + .expectBody(String.class) + .isEqualTo("Three"))); + } + + @Test + void postToRequestMappingWithReadOnlyAccessRespondsWith405() { + this.contextRunner.withPropertyValues("endpoint-access=READ_ONLY") + .run(withWebTestClient((webTestClient) -> webTestClient.post() + .uri("/actuator/example/three") + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.METHOD_NOT_ALLOWED))); + } + private ContextConsumer withWebTestClient(Consumer webClient) { return (context) -> { int port = ((AnnotationConfigServletWebServerApplicationContext) context.getSourceApplicationContext()) @@ -134,9 +206,15 @@ ControllerEndpointDiscoverer webEndpointDiscoverer(ApplicationContext applicatio } @Bean - ControllerEndpointHandlerMapping webEndpointHandlerMapping(ControllerEndpointsSupplier endpointsSupplier) { + ControllerEndpointHandlerMapping webEndpointHandlerMapping(ControllerEndpointsSupplier endpointsSupplier, + EndpointAccessResolver endpointAccessResolver) { return new ControllerEndpointHandlerMapping(new EndpointMapping("actuator"), - endpointsSupplier.getEndpoints(), null); + endpointsSupplier.getEndpoints(), null, endpointAccessResolver); + } + + @Bean + EndpointAccessResolver endpointAccessResolver(Environment environment) { + return (id, defaultAccess) -> environment.getProperty("endpoint-access", Access.class, Access.UNRESTRICTED); } } @@ -154,6 +232,11 @@ ResponseEntity two(@RequestBody Map content) { return ResponseEntity.created(URI.create("/example/" + content.get("id"))).build(); } + @RequestMapping(path = "/three", produces = MediaType.TEXT_PLAIN_VALUE) + String three() { + return "Three"; + } + } } 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..5efcf101cc6a 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. @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; @@ -42,7 +43,10 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support */ +@Deprecated(since = "3.3.5", forRemoval = true) +@SuppressWarnings("removal") class ControllerEndpointHandlerMappingTests { private final StaticApplicationContext context = new StaticApplicationContext(); @@ -92,7 +96,7 @@ void mappingWithNoPath() throws Exception { private ControllerEndpointHandlerMapping createMapping(String prefix, ExposableControllerEndpoint... endpoints) { ControllerEndpointHandlerMapping mapping = new ControllerEndpointHandlerMapping(new EndpointMapping(prefix), - Arrays.asList(endpoints), null); + Arrays.asList(endpoints), null, (endpointId, defaultAccess) -> Access.UNRESTRICTED); mapping.setApplicationContext(this.context); mapping.afterPropertiesSet(); return mapping; 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..2327785100e1 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)); @@ -243,7 +243,7 @@ private void customize(ResourceConfig config) { EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT; WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext, new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(), - Collections.emptyList()); + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); Collection resources = new JerseyEndpointResourceFactory().createEndpointResources( new EndpointMapping("/actuator"), discoverer.getEndpoints(), endpointMediaTypes, new EndpointLinksResolver(discoverer.getEndpoints()), true); @@ -288,8 +288,8 @@ HttpHandler httpHandler(ApplicationContext applicationContext) { WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping() { EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT; WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext, - new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(), - Collections.emptyList()); + new ConversionServiceParameterValueMapper(), endpointMediaTypes, Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); return new WebFluxEndpointHandlerMapping(new EndpointMapping("/actuator"), discoverer.getEndpoints(), endpointMediaTypes, new CorsConfiguration(), new EndpointLinksResolver(discoverer.getEndpoints()), true); @@ -317,8 +317,8 @@ TomcatServletWebServerFactory tomcat() { WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping() { EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT; WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext, - new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(), - Collections.emptyList()); + new ConversionServiceParameterValueMapper(), endpointMediaTypes, Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); return new WebMvcEndpointHandlerMapping(new EndpointMapping("/actuator"), discoverer.getEndpoints(), endpointMediaTypes, new CorsConfiguration(), new EndpointLinksResolver(discoverer.getEndpoints()), true); 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 78d08e20e09e..000000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicatorTests.java +++ /dev/null @@ -1,68 +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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.2.0", forRemoval = true) -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/info/SslInfoContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/SslInfoContributorTests.java new file mode 100644 index 000000000000..adfff88f00bd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/SslInfoContributorTests.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.info; + +import java.time.Duration; + +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.SslInfoContributor.SslInfoContributorRuntimeHints; +import org.springframework.boot.info.SslInfo; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SslInfoContributor}. + * + * @author Jonatan Ivanov + */ +class SslInfoContributorTests { + + @Test + void sslInfoShouldBeAdded() { + SslBundles sslBundles = new DefaultSslBundleRegistry("test", mock(SslBundle.class)); + SslInfo sslInfo = new SslInfo(sslBundles, Duration.ofDays(14)); + SslInfoContributor sslInfoContributor = new SslInfoContributor(sslInfo); + Info.Builder builder = new Info.Builder(); + sslInfoContributor.contribute(builder); + Info info = builder.build(); + assertThat(info.getDetails().get("ssl")).isInstanceOf(SslInfo.class); + } + + @Test + void shouldRegisterHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + new SslInfoContributorRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection() + .onType(SslInfo.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/metrics/cache/CacheMetricsRegistrarTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/CacheMetricsRegistrarTests.java index 8b6dbc783a09..e0894f539d0c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/CacheMetricsRegistrarTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/CacheMetricsRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 +41,9 @@ class CacheMetricsRegistrarTests { void bindToSupportedCache() { CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry, Collections.singleton(new CaffeineCacheMeterBinderProvider())); - assertThat(registrar.bindCacheToRegistry(new CaffeineCache("test", Caffeine.newBuilder().build()))).isTrue(); + assertThat( + registrar.bindCacheToRegistry(new CaffeineCache("test", Caffeine.newBuilder().recordStats().build()))) + .isTrue(); assertThat(this.meterRegistry.get("cache.gets").tags("name", "test").meter()).isNotNull(); } @@ -49,8 +51,8 @@ void bindToSupportedCache() { void bindToSupportedCacheWrappedInTransactionProxy() { CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry, Collections.singleton(new CaffeineCacheMeterBinderProvider())); - assertThat(registrar.bindCacheToRegistry( - new TransactionAwareCacheDecorator(new CaffeineCache("test", Caffeine.newBuilder().build())))) + assertThat(registrar.bindCacheToRegistry(new TransactionAwareCacheDecorator( + new CaffeineCache("test", Caffeine.newBuilder().recordStats().build())))) .isTrue(); assertThat(this.meterRegistry.get("cache.gets").tags("name", "test").meter()).isNotNull(); } @@ -58,7 +60,9 @@ void bindToSupportedCacheWrappedInTransactionProxy() { @Test void bindToUnsupportedCache() { CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry, Collections.emptyList()); - assertThat(registrar.bindCacheToRegistry(new CaffeineCache("test", Caffeine.newBuilder().build()))).isFalse(); + assertThat( + registrar.bindCacheToRegistry(new CaffeineCache("test", Caffeine.newBuilder().recordStats().build()))) + .isFalse(); assertThat(this.meterRegistry.find("cache.gets").tags("name", "test").meter()).isNull(); } 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..2b2a0aa328c1 --- /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({ "deprecation", "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/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..7bab283918b5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 @@ import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.CustomTriggerTaskDescriptor; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.FixedDelayTaskDescriptor; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.FixedRateTaskDescriptor; +import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.LastExecution; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.ScheduledTasksDescriptor; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.ScheduledTasksEndpointRuntimeHints; +import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.TaskDescriptor; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -42,6 +44,7 @@ import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskHolder; import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.scheduling.config.TaskExecutionOutcome.Status; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; @@ -52,6 +55,7 @@ * * @author Andy Wilkinson * @author Moritz Halbritter + * @author Brian Clozel */ class ScheduledTasksEndpointTests { @@ -68,6 +72,8 @@ void cronScheduledMethodIsReported() { CronTaskDescriptor description = (CronTaskDescriptor) tasks.getCron().get(0); assertThat(description.getExpression()).isEqualTo("0 0 0/3 1/1 * ?"); assertThat(description.getRunnable().getTarget()).isEqualTo(CronScheduledMethod.class.getName() + ".cron"); + assertThat(description.getNextExecution().getTime()).isInTheFuture(); + assertThat(description.getLastExecution()).isNull(); }); } @@ -80,7 +86,8 @@ 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()); + assertThat(description.getLastExecution()).isNull(); }); } @@ -92,10 +99,11 @@ void fixedDelayScheduledMethodIsReported() { assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getFixedDelay()).hasSize(1); FixedDelayTaskDescriptor description = (FixedDelayTaskDescriptor) tasks.getFixedDelay().get(0); - assertThat(description.getInitialDelay()).isEqualTo(2); - assertThat(description.getInterval()).isOne(); + assertThat(description.getInitialDelay()).isEqualTo(2000); + assertThat(description.getInterval()).isEqualTo(1000); assertThat(description.getRunnable().getTarget()) .isEqualTo(FixedDelayScheduledMethod.class.getName() + ".fixedDelay"); + assertThat(description.getLastExecution()).isNull(); }); } @@ -109,7 +117,8 @@ 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()); + assertThat(description.getLastExecution()).isNull(); }); } @@ -123,7 +132,8 @@ 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()); + assertThatTaskMayHaveBeenExecuted(description); }); } @@ -135,10 +145,11 @@ void fixedRateScheduledMethodIsReported() { assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getFixedRate()).hasSize(1); FixedRateTaskDescriptor description = (FixedRateTaskDescriptor) tasks.getFixedRate().get(0); - assertThat(description.getInitialDelay()).isEqualTo(4); - assertThat(description.getInterval()).isEqualTo(3); + assertThat(description.getInitialDelay()).isEqualTo(4000); + assertThat(description.getInterval()).isEqualTo(3000); assertThat(description.getRunnable().getTarget()) .isEqualTo(FixedRateScheduledMethod.class.getName() + ".fixedRate"); + assertThat(description.getLastExecution()).isNull(); }); } @@ -152,7 +163,8 @@ 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()); + assertThat(description.getLastExecution()).isNull(); }); } @@ -166,7 +178,8 @@ 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()); + assertThatTaskMayHaveBeenExecuted(description); }); } @@ -178,8 +191,9 @@ 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()); + assertThatTaskMayHaveBeenExecuted(description); }); } @@ -197,6 +211,16 @@ void shouldRegisterHints() { } } + private void assertThatTaskMayHaveBeenExecuted(TaskDescriptor descriptor) { + LastExecution lastExecution = descriptor.getLastExecution(); + if (lastExecution != null) { + if (lastExecution.getStatus() == Status.SUCCESS) { + assertThat(lastExecution.getTime()).isInThePast(); + assertThat(lastExecution.getException()).isNull(); + } + } + } + private void run(Class configuration, Consumer consumer) { this.contextRunner.withUserConfiguration(configuration) .run((context) -> consumer.accept(context.getBean(ScheduledTasksEndpoint.class).scheduledTasks())); @@ -215,7 +239,7 @@ ScheduledTasksEndpoint endpoint(Collection scheduledTaskHol static class FixedDelayScheduledMethod { - @Scheduled(fixedDelay = 1, initialDelay = 2) + @Scheduled(fixedDelay = 1000, initialDelay = 2000) void fixedDelay() { } @@ -224,7 +248,7 @@ void fixedDelay() { static class FixedRateScheduledMethod { - @Scheduled(fixedRate = 3, initialDelay = 4) + @Scheduled(fixedRate = 3000, initialDelay = 4000) void fixedRate() { } 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..7760555e28dd 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 @@ -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,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/security/AuthorizationAuditListenerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.java index ca66cb891fbb..0dd892fd8d96 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.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.context.ApplicationEventPublisher; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.event.AuthorizationDeniedEvent; import org.springframework.security.authorization.event.AuthorizationEvent; @@ -48,7 +49,7 @@ void init() { @Test void authorizationDeniedEvent() { - AuthorizationDecision decision = new AuthorizationDecision(false); + AuthorizationResult decision = new AuthorizationDecision(false); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("spring", "password"); authentication.setDetails("details"); @@ -62,7 +63,7 @@ void authorizationDeniedEvent() { @Test void authorizationDeniedEventWhenAuthenticationIsNotAvailable() { - AuthorizationDecision decision = new AuthorizationDecision(false); + AuthorizationResult decision = new AuthorizationDecision(false); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("spring", "password"); authentication.setDetails("details"); @@ -77,7 +78,7 @@ void authorizationDeniedEventWhenAuthenticationIsNotAvailable() { @Test void authorizationDeniedEventWhenAuthenticationDoesNotHaveDetails() { - AuthorizationDecision decision = new AuthorizationDecision(false); + AuthorizationResult decision = new AuthorizationDecision(false); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("spring", "password"); AuthorizationDeniedEvent authorizationEvent = new AuthorizationDeniedEvent<>(() -> authentication, "", 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/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java new file mode 100644 index 000000000000..74475c5ebf42 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.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.ssl; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.info.SslInfo; +import org.springframework.boot.info.SslInfo.BundleInfo; +import org.springframework.boot.info.SslInfo.CertificateChainInfo; +import org.springframework.boot.info.SslInfo.CertificateInfo; +import org.springframework.boot.info.SslInfo.CertificateValidityInfo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SslHealthIndicator}. + * + * @author Jonatan Ivanov + */ +class SslHealthIndicatorTests { + + private HealthIndicator healthIndicator; + + private CertificateValidityInfo validity; + + @BeforeEach + void setUp() { + SslInfo sslInfo = mock(SslInfo.class); + BundleInfo bundle = mock(BundleInfo.class); + CertificateChainInfo certificateChain = mock(CertificateChainInfo.class); + CertificateInfo certificateInfo = mock(CertificateInfo.class); + this.healthIndicator = new SslHealthIndicator(sslInfo); + this.validity = mock(CertificateValidityInfo.class); + given(sslInfo.getBundles()).willReturn(List.of(bundle)); + given(bundle.getCertificateChains()).willReturn(List.of(certificateChain)); + given(certificateChain.getCertificates()).willReturn(List.of(certificateInfo)); + given(certificateInfo.getValidity()).willReturn(this.validity); + } + + @Test + void shouldBeUpIfNoSslIssuesDetected() { + given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.VALID); + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).hasSize(1); + assertThat(validChains.get(0)).isInstanceOf(CertificateChainInfo.class); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).isEmpty(); + } + + @Test + void shouldBeOutOfServiceIfACertificateIsExpired() { + given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.EXPIRED); + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).isEmpty(); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains.get(0)).isInstanceOf(CertificateChainInfo.class); + } + + @Test + void shouldBeOutOfServiceIfACertificateIsNotYetValid() { + given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.NOT_YET_VALID); + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).isEmpty(); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains.get(0)).isInstanceOf(CertificateChainInfo.class); + + } + + @Test + void shouldReportWarningIfACertificateWillExpireSoon() { + given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.WILL_EXPIRE_SOON); + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).hasSize(1); + assertThat(validChains.get(0)).isInstanceOf(CertificateChainInfo.class); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).isEmpty(); + } + + private static void assertDetailsKeys(Health health) { + assertThat(health.getDetails()).containsOnlyKeys("validChains", "invalidChains"); + } + + @SuppressWarnings("unchecked") + private static List getInvalidChains(Health health) { + return (List) health.getDetails().get("invalidChains"); + } + + @SuppressWarnings("unchecked") + private static List getValidChains(Health health) { + return (List) health.getDetails().get("validChains"); + } + +} 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 1888ed5ca1f7..6d062d9ce6c2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -1,6 +1,5 @@ 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.deployed" @@ -10,12 +9,24 @@ plugins { 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("com.redis:testcontainers-redis") dockerTestImplementation("org.assertj:assertj-core") + dockerTestImplementation("org.awaitility:awaitility") dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestImplementation("org.mockito:mockito-core") dockerTestImplementation("org.springframework:spring-test") @@ -50,6 +61,7 @@ dependencies { 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") @@ -64,7 +76,8 @@ 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-broker") + optional("org.apache.activemq:activemq-client") optional("org.apache.activemq:artemis-jakarta-client") { exclude group: "commons-logging", module: "commons-logging" } @@ -110,6 +123,7 @@ 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") @@ -121,17 +135,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" @@ -263,6 +271,7 @@ dependencies { 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/dockerTest/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 index 5d89b6c9aeca..666cae4a8f1b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 @@ -19,7 +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.cassandra.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -44,7 +44,7 @@ class CassandraAutoConfigurationIntegrationTests { @Container - static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); + 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/dockerTest/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 index 4f144c65d592..fec320466a50 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 @@ -27,7 +27,7 @@ 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.cassandra.CassandraContainer; import org.testcontainers.containers.ContainerLaunchException; import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; import org.testcontainers.images.builder.Transferable; @@ -53,8 +53,7 @@ class CassandraAutoConfigurationWithPasswordAuthenticationIntegrationTests { @Container - static final PasswordAuthenticatorCassandraContainer cassandra = TestImage - .container(PasswordAuthenticatorCassandraContainer.class) + static final CassandraContainer cassandra = TestImage.container(PasswordAuthenticatorCassandraContainer.class) .withStartupAttempts(5) .waitingFor(new CassandraWaitStrategy()); @@ -85,8 +84,7 @@ void authenticationWithInvalidCredentials() { .withMessageContaining("Authentication error")); } - static final class PasswordAuthenticatorCassandraContainer - extends CassandraContainer { + static final class PasswordAuthenticatorCassandraContainer extends CassandraContainer { PasswordAuthenticatorCassandraContainer(DockerImageName dockerImageName) { super(dockerImageName); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 index fd5c2727d62b..eb125d06ff0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 @@ -19,7 +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.cassandra.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,7 +47,7 @@ class CassandraDataAutoConfigurationIntegrationTests { @Container - static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration( diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 index 7e22b198f9fa..e631f8be2b82 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.data.redis; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +30,6 @@ 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.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; 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/pulsar/PulsarAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationIntegrationTests.java index fc9d6ccd9082..b95f8477dc80 100644 --- 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 @@ -19,7 +19,6 @@ 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; @@ -106,7 +105,7 @@ static class TestWebController { } @GetMapping("/hello") - String sayHello() throws PulsarClientException { + String sayHello() { return "Hello World -> " + this.pulsarTemplate.send(TOPIC, "hello"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 index 55d55bb4e9d1..5387e5579e0b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 @@ -18,28 +18,38 @@ import java.time.Duration; import java.util.List; +import java.util.Map; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; 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.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.data.redis.connection.ReactiveRedisConnection; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.http.ResponseCookie; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; import org.springframework.session.data.mongo.ReactiveMongoSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository; import org.springframework.session.data.redis.ReactiveRedisSessionRepository; +import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction; +import org.springframework.session.data.redis.config.annotation.ConfigureNotifyKeyspaceEventsReactiveAction; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Reactive Redis-specific tests for {@link SessionAutoConfiguration}. @@ -121,6 +131,52 @@ void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() { })); } + @Test + void indexedRedisSessionDefaultConfig() { + this.contextRunner + .withPropertyValues("spring.session.redis.repository-type=indexed", + "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .run(validateSpringSessionUsesIndexedRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE)); + } + + @Test + void indexedRedisSessionStoreWithCustomizations() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.namespace=foo", + "spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(), + "spring.data.redis.port=" + redis.getFirstMappedPort()) + .run(validateSpringSessionUsesIndexedRedis("foo:", SaveMode.ON_GET_ATTRIBUTE)); + } + + @Test + void indexedRedisSessionWithConfigureActionNone() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("spring.session.redis.repository-type=indexed", + "spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(), + "spring.data.redis.port=" + redis.getFirstMappedPort()) + .run(validateStrategy(ConfigureReactiveRedisAction.NO_OP.getClass())); + } + + @Test + void indexedRedisSessionWithDefaultConfigureActionNone() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("spring.session.redis.repository-type=indexed", + "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) + .run(validateStrategy(ConfigureNotifyKeyspaceEventsReactiveAction.class, + entry("notify-keyspace-events", "gxE"))); + } + + @Test + void indexedRedisSessionWithCustomConfigureReactiveRedisActionBean() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withUserConfiguration(MaxEntriesReactiveRedisAction.class) + .withPropertyValues("spring.session.redis.repository-type=indexed", + "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) + .run(validateStrategy(MaxEntriesReactiveRedisAction.class, entry("set-max-intset-entries", "1024"))); + + } + private ContextConsumer validateSpringSessionUsesRedis(String namespace, SaveMode saveMode) { return (context) -> { @@ -133,4 +189,42 @@ private ContextConsumer validateSpringS }; } + private ContextConsumer validateSpringSessionUsesIndexedRedis( + String keyNamespace, SaveMode saveMode) { + return (context) -> { + ReactiveRedisIndexedSessionRepository repository = validateSessionRepository(context, + ReactiveRedisIndexedSessionRepository.class); + assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", + new ServerProperties().getReactive().getSession().getTimeout()); + assertThat(repository).hasFieldOrPropertyWithValue("namespace", keyNamespace); + assertThat(repository).hasFieldOrPropertyWithValue("saveMode", saveMode); + }; + } + + private ContextConsumer validateStrategy( + Class expectedConfigureReactiveRedisActionType, + Map.Entry... expectedConfig) { + return (context) -> { + assertThat(context).hasSingleBean(ConfigureReactiveRedisAction.class); + assertThat(context).hasSingleBean(RedisConnectionFactory.class); + assertThat(context.getBean(ConfigureReactiveRedisAction.class)) + .isInstanceOf(expectedConfigureReactiveRedisActionType); + ReactiveRedisConnection connection = context.getBean(ReactiveRedisConnectionFactory.class) + .getReactiveConnection(); + if (expectedConfig.length > 0) { + assertThat(connection.serverCommands().getConfig("*").block(Duration.ofSeconds(30))) + .contains(expectedConfig); + } + }; + } + + static class MaxEntriesReactiveRedisAction implements ConfigureReactiveRedisAction { + + @Override + public Mono configure(ReactiveRedisConnection connection) { + return Mono.when(connection.serverCommands().setConfig("set-max-intset-entries", "1024")); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 index eecebb7ad9ca..5233fd3ad920 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/dockerTest/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 @@ -19,6 +19,7 @@ import java.time.Duration; import java.util.Map; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -31,7 +32,6 @@ 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.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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/AutoConfigurationExcludeFilter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java index ab691e235efe..40e4ef635f08 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,8 +63,8 @@ private boolean isAutoConfiguration(MetadataReader metadataReader) { protected List getAutoConfigurations() { if (this.autoConfigurations == null) { - this.autoConfigurations = ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader) - .getCandidates(); + ImportCandidates importCandidates = ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader); + this.autoConfigurations = importCandidates.getCandidates(); } return this.autoConfigurations; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java index e9e9f1642920..e3da084cf8f9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java @@ -76,6 +76,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { + static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1; + private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry(); private static final String[] NO_IMPORTS = {}; @@ -84,6 +86,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude"; + private final Class autoConfigurationAnnotation; + private ConfigurableListableBeanFactory beanFactory; private Environment environment; @@ -92,7 +96,18 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector, private ResourceLoader resourceLoader; - private ConfigurationClassFilter configurationClassFilter; + private volatile ConfigurationClassFilter configurationClassFilter; + + private volatile AutoConfigurationReplacements autoConfigurationReplacements; + + public AutoConfigurationImportSelector() { + this(null); + } + + AutoConfigurationImportSelector(Class autoConfigurationAnnotation) { + this.autoConfigurationAnnotation = (autoConfigurationAnnotation != null) ? autoConfigurationAnnotation + : AutoConfiguration.class; + } @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { @@ -177,11 +192,12 @@ protected Class getAnnotationClass() { * @return a list of candidate configurations */ protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { - List configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()) - .getCandidates(); + ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, + getBeanClassLoader()); + List configurations = importCandidates.getCandidates(); Assert.notEmpty(configurations, - "No auto configuration classes found in " - + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you " + "No auto configuration classes found in " + "META-INF/spring/" + + this.autoConfigurationAnnotation.getName() + ".imports. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } @@ -225,7 +241,7 @@ protected Set getExclusions(AnnotationMetadata metadata, AnnotationAttri excluded.addAll(asList(attributes, "exclude")); excluded.addAll(asList(attributes, "excludeName")); excluded.addAll(getExcludeAutoConfigurationsProperty()); - return excluded; + return getAutoConfigurationReplacements().replaceAll(excluded); } /** @@ -254,14 +270,26 @@ protected List getAutoConfigurationImportFilters( } private ConfigurationClassFilter getConfigurationClassFilter() { - if (this.configurationClassFilter == null) { + ConfigurationClassFilter configurationClassFilter = this.configurationClassFilter; + if (configurationClassFilter == null) { List filters = getAutoConfigurationImportFilters(); for (AutoConfigurationImportFilter filter : filters) { invokeAwareMethods(filter); } - this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters); + configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters); + this.configurationClassFilter = configurationClassFilter; + } + return configurationClassFilter; + } + + private AutoConfigurationReplacements getAutoConfigurationReplacements() { + AutoConfigurationReplacements autoConfigurationReplacements = this.autoConfigurationReplacements; + if (autoConfigurationReplacements == null) { + autoConfigurationReplacements = AutoConfigurationReplacements.load(this.autoConfigurationAnnotation, + this.beanClassLoader); + this.autoConfigurationReplacements = autoConfigurationReplacements; } - return this.configurationClassFilter; + return autoConfigurationReplacements; } protected final List removeDuplicates(List list) { @@ -344,7 +372,7 @@ protected final ResourceLoader getResourceLoader() { @Override public int getOrder() { - return Ordered.LOWEST_PRECEDENCE - 1; + return ORDER; } private static class ConfigurationClassFilter { @@ -405,6 +433,8 @@ private static final class AutoConfigurationGroup private AutoConfigurationMetadata autoConfigurationMetadata; + private AutoConfigurationReplacements autoConfigurationReplacements; + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; @@ -426,7 +456,15 @@ public void process(AnnotationMetadata annotationMetadata, DeferredImportSelecto () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); - AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) + AutoConfigurationImportSelector autoConfigurationImportSelector = (AutoConfigurationImportSelector) deferredImportSelector; + AutoConfigurationReplacements autoConfigurationReplacements = autoConfigurationImportSelector + .getAutoConfigurationReplacements(); + Assert.state( + this.autoConfigurationReplacements == null + || this.autoConfigurationReplacements.equals(autoConfigurationReplacements), + "Auto-configuration replacements must be the same for each call to process"); + this.autoConfigurationReplacements = autoConfigurationReplacements; + AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector .getAutoConfigurationEntry(annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { @@ -448,7 +486,6 @@ public Iterable selectImports() { .flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); processedConfigurations.removeAll(allExclusions); - return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream() .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) .toList(); @@ -463,7 +500,8 @@ private AutoConfigurationMetadata getAutoConfigurationMetadata() { private List sortAutoConfigurations(Set configurations, AutoConfigurationMetadata autoConfigurationMetadata) { - return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata) + return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata, + this.autoConfigurationReplacements::replace) .getInPriorityOrder(configurations); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReplacements.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReplacements.java new file mode 100644 index 000000000000..88846230c0ea --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationReplacements.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.autoconfigure; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.springframework.boot.context.annotation.ImportCandidates; +import org.springframework.core.io.UrlResource; +import org.springframework.util.Assert; + +/** + * Contains auto-configuration replacements used to handle deprecated or moved + * auto-configurations which may still be referenced by + * {@link AutoConfigureBefore @AutoConfigureBefore}, + * {@link AutoConfigureAfter @AutoConfigureAfter} or exclusions. + * + * @author Phillip Webb + */ +final class AutoConfigurationReplacements { + + private static final String LOCATION = "META-INF/spring/%s.replacements"; + + private final Map replacements; + + private AutoConfigurationReplacements(Map replacements) { + this.replacements = Map.copyOf(replacements); + } + + Set replaceAll(Set classNames) { + Set replaced = new LinkedHashSet<>(classNames.size()); + for (String className : classNames) { + replaced.add(replace(className)); + } + return replaced; + } + + String replace(String className) { + return this.replacements.getOrDefault(className, className); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return this.replacements.equals(((AutoConfigurationReplacements) obj).replacements); + } + + @Override + public int hashCode() { + return this.replacements.hashCode(); + } + + /** + * Loads the relocations from the classpath. Relactions are stored in files named + * {@code META-INF/spring/full-qualified-annotation-name.replacements} on the + * classpath. The file is loaded using {@link Properties#load(java.io.InputStream)} + * with each entry containing an auto-configuration class name as the key and the + * replacement class name as the value. + * @param annotation annotation to load + * @param classLoader class loader to use for loading + * @return list of names of annotated classes + */ + static AutoConfigurationReplacements load(Class annotation, ClassLoader classLoader) { + Assert.notNull(annotation, "'annotation' must not be null"); + ClassLoader classLoaderToUse = decideClassloader(classLoader); + String location = String.format(LOCATION, annotation.getName()); + Enumeration urls = findUrlsInClasspath(classLoaderToUse, location); + Map replacements = new HashMap<>(); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + replacements.putAll(readReplacements(url)); + } + return new AutoConfigurationReplacements(replacements); + } + + private static ClassLoader decideClassloader(ClassLoader classLoader) { + if (classLoader == null) { + return ImportCandidates.class.getClassLoader(); + } + return classLoader; + } + + private static Enumeration findUrlsInClasspath(ClassLoader classLoader, String location) { + try { + return classLoader.getResources(location); + } + catch (IOException ex) { + throw new IllegalArgumentException("Failed to load configurations from location [" + location + "]", ex); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Map readReplacements(URL url) { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) { + Properties properties = new Properties(); + properties.load(reader); + return (Map) properties; + } + catch (IOException ex) { + throw new IllegalArgumentException("Unable to load replacements from location [" + url + "]", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java index 271fc11087d7..043da47b27c3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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; import java.io.IOException; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -27,6 +28,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.function.UnaryOperator; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; @@ -47,11 +49,14 @@ class AutoConfigurationSorter { private final AutoConfigurationMetadata autoConfigurationMetadata; + private final UnaryOperator replacementMapper; + AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory, - AutoConfigurationMetadata autoConfigurationMetadata) { + AutoConfigurationMetadata autoConfigurationMetadata, UnaryOperator replacementMapper) { Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null"); this.metadataReaderFactory = metadataReaderFactory; this.autoConfigurationMetadata = autoConfigurationMetadata; + this.replacementMapper = replacementMapper; } List getInPriorityOrder(Collection classNames) { @@ -108,7 +113,7 @@ private void checkForCycles(Set processing, String current, String after () -> "AutoConfigure cycle detected between " + current + " and " + after); } - private static class AutoConfigurationClasses { + private class AutoConfigurationClasses { private final Map classes = new LinkedHashMap<>(); @@ -157,7 +162,7 @@ Set getClassesRequestedAfter(String className) { } - private static class AutoConfigurationClass { + private class AutoConfigurationClass { private final String className; @@ -192,20 +197,36 @@ boolean isAvailable() { Set getBefore() { if (this.before == null) { - this.before = (wasProcessed() ? this.autoConfigurationMetadata.getSet(this.className, - "AutoConfigureBefore", Collections.emptySet()) : getAnnotationValue(AutoConfigureBefore.class)); + this.before = getClassNames("AutoConfigureBefore", AutoConfigureBefore.class); } return this.before; } Set getAfter() { if (this.after == null) { - this.after = (wasProcessed() ? this.autoConfigurationMetadata.getSet(this.className, - "AutoConfigureAfter", Collections.emptySet()) : getAnnotationValue(AutoConfigureAfter.class)); + this.after = getClassNames("AutoConfigureAfter", AutoConfigureAfter.class); } return this.after; } + private Set getClassNames(String metadataKey, Class annotation) { + Set annotationValue = wasProcessed() + ? this.autoConfigurationMetadata.getSet(this.className, metadataKey, Collections.emptySet()) + : getAnnotationValue(annotation); + return applyReplacements(annotationValue); + } + + private Set applyReplacements(Set values) { + if (AutoConfigurationSorter.this.replacementMapper == null) { + return values; + } + Set replaced = new LinkedHashSet<>(values); + for (String value : values) { + replaced.add(AutoConfigurationSorter.this.replacementMapper.apply(value)); + } + return replaced; + } + private int getOrder() { if (wasProcessed()) { return this.autoConfigurationMetadata.getInteger(this.className, "AutoConfigureOrder", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurations.java index 2063a69fb249..9c0b22bf9e76 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurations.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.Collection; import java.util.List; import java.util.Set; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.springframework.boot.context.annotation.Configurations; @@ -36,32 +37,43 @@ */ public class AutoConfigurations extends Configurations implements Ordered { - private static final AutoConfigurationSorter SORTER = new AutoConfigurationSorter(new SimpleMetadataReaderFactory(), - null); + private static final SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); - private static final Ordered ORDER = new AutoConfigurationImportSelector(); + private static final int ORDER = AutoConfigurationImportSelector.ORDER; + + static final AutoConfigurationReplacements replacements = AutoConfigurationReplacements + .load(AutoConfiguration.class, null); + + private final UnaryOperator replacementMapper; protected AutoConfigurations(Collection> classes) { - super(classes); + this(replacements::replace, classes); } - @Override - protected Collection> sort(Collection> classes) { - List names = classes.stream().map(Class::getName).toList(); - List sorted = SORTER.getInPriorityOrder(names); - return sorted.stream() - .map((className) -> ClassUtils.resolveClassName(className, null)) - .collect(Collectors.toCollection(ArrayList::new)); + AutoConfigurations(UnaryOperator replacementMapper, Collection> classes) { + super(sorter(replacementMapper), classes); + this.replacementMapper = replacementMapper; + } + + private static UnaryOperator>> sorter(UnaryOperator replacementMapper) { + AutoConfigurationSorter sorter = new AutoConfigurationSorter(metadataReaderFactory, null, replacementMapper); + return (classes) -> { + List names = classes.stream().map(Class::getName).map(replacementMapper::apply).toList(); + List sorted = sorter.getInPriorityOrder(names); + return sorted.stream() + .map((className) -> ClassUtils.resolveClassName(className, null)) + .collect(Collectors.toCollection(ArrayList::new)); + }; } @Override public int getOrder() { - return ORDER.getOrder(); + return ORDER; } @Override protected AutoConfigurations merge(Set> mergedClasses) { - return new AutoConfigurations(mergedClasses); + return new AutoConfigurations(this.replacementMapper, mergedClasses); } public static AutoConfigurations of(Class... classes) { 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 bcfe04a6748f..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. @@ -134,6 +134,7 @@ protected void configure(T factory, ConnectionFactory connectionFactory, 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/PropertiesRabbitConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetails.java index 1ac95b513fd5..5f4a31398b3e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetails.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ public String getVirtualHost() { @Override public List
getAddresses() { List
addresses = new ArrayList<>(); - for (String address : this.properties.determineAddresses().split(",")) { + for (String address : this.properties.determineAddresses()) { int portSeparatorIndex = address.lastIndexOf(':'); String host = address.substring(0, portSeparatorIndex); String port = address.substring(portSeparatorIndex + 1); 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 4c56a677c830..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. @@ -123,17 +123,14 @@ CachingConnectionFactory rabbitConnectionFactory( RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, ObjectProvider connectionFactoryCustomizers) throws Exception { - 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/RabbitProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java index 53f561a30a6e..1510e9adbed7 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 @@ -48,6 +48,7 @@ * @author Rafael Carvalho * @author Scott Frederick * @author Lasse Wulff + * @author Yanming Zhou * @since 1.0.0 */ @ConfigurationProperties(prefix = "spring.rabbitmq") @@ -91,10 +92,10 @@ public class RabbitProperties { private String virtualHost; /** - * Comma-separated list of addresses to which the client should connect. When set, the - * host and port are ignored. + * List of addresses to which the client should connect. When set, the host and port + * are ignored. */ - private String addresses; + private List addresses; /** * Mode used to shuffle configured addresses. @@ -162,7 +163,7 @@ public String getHost() { * Returns the host from the first address, or the configured host if no addresses * have been set. * @return the host - * @see #setAddresses(String) + * @see #setAddresses(List) * @see #getHost() */ public String determineHost() { @@ -184,7 +185,7 @@ public Integer getPort() { * Returns the port from the first address, or the configured port if no addresses * have been set. * @return the port - * @see #setAddresses(String) + * @see #setAddresses(List) * @see #getPort() */ public int determinePort() { @@ -202,38 +203,38 @@ public void setPort(Integer port) { this.port = port; } - public String getAddresses() { + public List getAddresses() { return this.addresses; } /** - * Returns the comma-separated addresses or a single address ({@code host:port}) - * created from the configured host and port if no addresses have been set. + * Returns the configured addresses or a single address ({@code host:port}) created + * from the configured host and port if no addresses have been set. * @return the addresses */ - public String determineAddresses() { + public List determineAddresses() { if (CollectionUtils.isEmpty(this.parsedAddresses)) { if (this.host.contains(",")) { throw new InvalidConfigurationPropertyValueException("spring.rabbitmq.host", this.host, "Invalid character ','. Value must be a single host. For multiple hosts, use property 'spring.rabbitmq.addresses' instead."); } - return this.host + ":" + determinePort(); + return List.of(this.host + ":" + determinePort()); } List addressStrings = new ArrayList<>(); for (Address parsedAddress : this.parsedAddresses) { addressStrings.add(parsedAddress.host + ":" + parsedAddress.port); } - return StringUtils.collectionToCommaDelimitedString(addressStrings); + return addressStrings; } - public void setAddresses(String addresses) { + public void setAddresses(List addresses) { this.addresses = addresses; this.parsedAddresses = parseAddresses(addresses); } - private List
parseAddresses(String addresses) { + private List
parseAddresses(List addresses) { List
parsedAddresses = new ArrayList<>(); - for (String address : StringUtils.commaDelimitedListToStringArray(addresses)) { + for (String address : addresses) { parsedAddresses.add(new Address(address, Optional.ofNullable(getSsl().getEnabled()).orElse(false))); } return parsedAddresses; @@ -247,7 +248,7 @@ public String getUsername() { * If addresses have been set and the first address has a username it is returned. * Otherwise returns the result of calling {@code getUsername()}. * @return the username - * @see #setAddresses(String) + * @see #setAddresses(List) * @see #getUsername() */ public String determineUsername() { @@ -270,7 +271,7 @@ public String getPassword() { * If addresses have been set and the first address has a password it is returned. * Otherwise returns the result of calling {@code getPassword()}. * @return the password or {@code null} - * @see #setAddresses(String) + * @see #setAddresses(List) * @see #getPassword() */ public String determinePassword() { @@ -297,7 +298,7 @@ public String getVirtualHost() { * If addresses have been set and the first address has a virtual host it is returned. * Otherwise returns the result of calling {@code getVirtualHost()}. * @return the virtual host or {@code null} - * @see #setAddresses(String) + * @see #setAddresses(List) * @see #getVirtualHost() */ public String determineVirtualHost() { @@ -470,7 +471,7 @@ public Boolean getEnabled() { * Returns whether SSL is enabled from the first address, or the configured ssl * enabled flag if no addresses have been set. * @return whether ssl is enabled - * @see #setAddresses(String) + * @see #setAddresses(List) * @see #getEnabled() () */ public boolean determineEnabled() { @@ -719,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 { @@ -997,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; } @@ -1049,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 { 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 a160193ff248..6e3047d1dd53 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 @@ -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; 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/batch/BatchAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java index 7f31db1fb7a5..c37293f31735 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; @@ -43,6 +44,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.task.TaskExecutor; import org.springframework.jdbc.datasource.init.DatabasePopulator; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; @@ -62,6 +64,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 }) @@ -98,17 +102,26 @@ static class SpringBootBatchConfiguration extends DefaultBatchConfiguration { private final PlatformTransactionManager transactionManager; + private final TaskExecutor taskExector; + private final BatchProperties properties; 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, + @BatchTaskExecutor ObjectProvider batchTaskExecutor, BatchProperties properties, + ObjectProvider batchConversionServiceCustomizers, + ObjectProvider executionContextSerializer) { this.dataSource = batchDataSource.getIfAvailable(() -> dataSource); - this.transactionManager = transactionManager; + this.transactionManager = batchTransactionManager.getIfAvailable(() -> transactionManager); + this.taskExector = batchTaskExecutor.getIfAvailable(); this.properties = properties; this.batchConversionServiceCustomizers = batchConversionServiceCustomizers.orderedStream().toList(); + this.executionContextSerializer = executionContextSerializer.getIfAvailable(); } @Override @@ -142,6 +155,17 @@ protected ConfigurableConversionService getConversionService() { return conversionService; } + @Override + protected ExecutionContextSerializer getExecutionContextSerializer() { + return (this.executionContextSerializer != null) ? this.executionContextSerializer + : super.getExecutionContextSerializer(); + } + + @Override + protected TaskExecutor getTaskExecutor() { + return (this.taskExector != null) ? this.taskExector : super.getTaskExecutor(); + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchTaskExecutor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchTaskExecutor.java new file mode 100644 index 000000000000..4a623125ab69 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchTaskExecutor.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.core.task.TaskExecutor; + +/** + * Qualifier annotation for a {@link TaskExecutor} to be injected into Batch + * auto-configuration. Can be used on a secondary task executor source, if there is + * another one marked as {@link Primary @Primary}. + * + * @author Andy Wilkinson + * @since 3.4.0 + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier +public @interface BatchTaskExecutor { + +} 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/cache/CacheProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java index 2a26704960aa..62ce77a7184c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.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,8 +41,8 @@ public class CacheProperties { private CacheType type; /** - * Comma-separated list of cache names to create if supported by the underlying cache - * manager. Usually, this disables the ability to create additional caches on-the-fly. + * List of cache names to create if supported by the underlying cache manager. + * Usually, this disables the ability to create additional caches on-the-fly. */ private List cacheNames = new ArrayList<>(); 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..1663dd4742e1 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,15 @@ * 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/condition/ConditionalOnBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java index d710455fc652..fad856c3f9c1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java @@ -24,6 +24,8 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -34,8 +36,8 @@ * bean. *

* When placed on a {@link Bean @Bean} method and none of {@link #value}, {@link #type}, - * or {@link #name} has been specified, the bean type to match defaults to the return type - * of the {@code @Bean} method: + * {@link #name}, or {@link #annotation} has been specified, the bean type to match + * defaults to the return type of the {@code @Bean} method: * *

  * @Configuration
@@ -68,22 +70,38 @@
 
 	/**
 	 * The class types of beans that should be checked. The condition matches when beans
-	 * of all classes specified are contained in the {@link BeanFactory}.
+	 * of all classes specified are contained in the {@link BeanFactory}. Beans that are
+	 * not autowire candidates or that are not default candidates are ignored.
 	 * @return the class types of beans to check
+	 * @see Bean#autowireCandidate()
+	 * @see BeanDefinition#isAutowireCandidate
+	 * @see Bean#defaultCandidate()
+	 * @see AbstractBeanDefinition#isDefaultCandidate
 	 */
 	Class[] value() default {};
 
 	/**
 	 * The class type names of beans that should be checked. The condition matches when
-	 * beans of all classes specified are contained in the {@link BeanFactory}.
+	 * beans of all classes specified are contained in the {@link BeanFactory}. Beans that
+	 * are not autowire candidates or that are not default candidates are ignored.
 	 * @return the class type names of beans to check
+	 * @see Bean#autowireCandidate()
+	 * @see BeanDefinition#isAutowireCandidate
+	 * @see Bean#defaultCandidate()
+	 * @see AbstractBeanDefinition#isDefaultCandidate
 	 */
 	String[] type() default {};
 
 	/**
 	 * The annotation type decorating a bean that should be checked. The condition matches
 	 * when all the annotations specified are defined on beans in the {@link BeanFactory}.
+	 * Beans that are not autowire candidates or that are not default candidates are
+	 * ignored.
 	 * @return the class-level annotation types to check
+	 * @see Bean#autowireCandidate()
+	 * @see BeanDefinition#isAutowireCandidate
+	 * @see Bean#defaultCandidate()
+	 * @see AbstractBeanDefinition#isDefaultCandidate
 	 */
 	Class[] annotation() default {};
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
index 0819e92bc39e..76245b82ef0e 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
@@ -24,6 +24,8 @@
 import java.lang.annotation.Target;
 
 import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Conditional;
 
@@ -34,8 +36,8 @@
  * the same bean.
  * 

* When placed on a {@link Bean @Bean} method and none of {@link #value}, {@link #type}, - * or {@link #name} has been specified, the bean type to match defaults to the return type - * of the {@code @Bean} method: + * {@link #name}, or {@link #annotation} has been specified, the bean type to match + * defaults to the return type of the {@code @Bean} method: * *

  * @Configuration
@@ -69,15 +71,25 @@
 
 	/**
 	 * The class types of beans that should be checked. The condition matches when no bean
-	 * of each class specified is contained in the {@link BeanFactory}.
+	 * of each class specified is contained in the {@link BeanFactory}. Beans that are not
+	 * autowire candidates or that are not default candidates are ignored.
 	 * @return the class types of beans to check
+	 * @see Bean#autowireCandidate()
+	 * @see BeanDefinition#isAutowireCandidate
+	 * @see Bean#defaultCandidate()
+	 * @see AbstractBeanDefinition#isDefaultCandidate
 	 */
 	Class[] value() default {};
 
 	/**
 	 * The class type names of beans that should be checked. The condition matches when no
-	 * bean of each class specified is contained in the {@link BeanFactory}.
+	 * bean of each class specified is contained in the {@link BeanFactory}. Beans that
+	 * are not autowire candidates or that are not default candidates are ignored.
 	 * @return the class type names of beans to check
+	 * @see Bean#autowireCandidate()
+	 * @see BeanDefinition#isAutowireCandidate
+	 * @see Bean#defaultCandidate()
+	 * @see AbstractBeanDefinition#isDefaultCandidate
 	 */
 	String[] type() default {};
 
@@ -99,8 +111,13 @@
 	/**
 	 * The annotation type decorating a bean that should be checked. The condition matches
 	 * when each annotation specified is missing from all beans in the
-	 * {@link BeanFactory}.
+	 * {@link BeanFactory}. Beans that are not autowire candidates or that are not default
+	 * candidates are ignored.
 	 * @return the class-level annotation types to check
+	 * @see Bean#autowireCandidate()
+	 * @see BeanDefinition#isAutowireCandidate
+	 * @see Bean#defaultCandidate()
+	 * @see AbstractBeanDefinition#isDefaultCandidate
 	 */
 	Class[] annotation() default {};
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java
index c1b79e7af14f..cb993223117b 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.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.
@@ -23,6 +23,9 @@
 import java.lang.annotation.Target;
 
 import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Conditional;
 
 /**
@@ -51,22 +54,32 @@
 	/**
 	 * The class type of bean that should be checked. The condition matches if a bean of
 	 * the class specified is contained in the {@link BeanFactory} and a primary candidate
-	 * exists in case of multiple instances.
+	 * exists in case of multiple instances. Beans that are not autowire candidates or
+	 * that are not default candidates are ignored.
 	 * 

* This attribute may not be used in conjunction with * {@link #type()}, but it may be used instead of {@link #type()}. * @return the class type of the bean to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate + * @see Bean#defaultCandidate() + * @see AbstractBeanDefinition#isDefaultCandidate */ Class value() default Object.class; /** * The class type name of bean that should be checked. The condition matches if a bean * of the class specified is contained in the {@link BeanFactory} and a primary - * candidate exists in case of multiple instances. + * candidate exists in case of multiple instances. Beans that are not autowire + * candidates or that are not default candidates are ignored. *

* This attribute may not be used in conjunction with * {@link #value()}, but it may be used instead of {@link #value()}. * @return the class type name of the bean to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate + * @see Bean#defaultCandidate() + * @see AbstractBeanDefinition#isDefaultCandidate */ String type() default ""; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java index f470245267c7..0d9788b09449 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.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. @@ -132,7 +132,7 @@ public boolean matches(String className, ClassLoader classLoader) { abstract boolean matches(String className, ClassLoader classLoader); - static boolean isPresent(String className, ClassLoader classLoader) { + private static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index b90ac437e933..9cceb27c7a9a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -24,19 +24,25 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.SingletonBeanRegistry; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style; import org.springframework.context.annotation.Bean; @@ -113,61 +119,90 @@ private ConditionOutcome getOutcome(Set requiredBeanTypes, Class spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class); - MatchResult matchResult = getMatchingBeans(context, spec); - if (!matchResult.isAllMatched()) { - String reason = createOnBeanNoMatchReason(matchResult); - return ConditionOutcome.noMatch(spec.message().because(reason)); + matchOutcome = evaluateConditionalOnBean(spec, matchOutcome.getConditionMessage()); + if (!matchOutcome.isMatch()) { + return matchOutcome; } - matchMessage = spec.message(matchMessage) - .found("bean", "beans") - .items(Style.QUOTE, matchResult.getNamesOfAllMatches()); } if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { - Spec spec = new SingleCandidateSpec(context, metadata, annotations); - MatchResult matchResult = getMatchingBeans(context, spec); - if (!matchResult.isAllMatched()) { - return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll()); - } - Set allBeans = matchResult.getNamesOfAllMatches(); - if (allBeans.size() == 1) { - matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans); - } - else { - List primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans, - spec.getStrategy() == SearchStrategy.ALL); - if (primaryBeans.isEmpty()) { - return ConditionOutcome - .noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans)); - } - if (primaryBeans.size() > 1) { - return ConditionOutcome - .noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans)); - } - matchMessage = spec.message(matchMessage) - .found("a single primary bean '" + primaryBeans.get(0) + "' from beans") - .items(Style.QUOTE, allBeans); + Spec spec = new SingleCandidateSpec(context, metadata, + metadata.getAnnotations()); + matchOutcome = evaluateConditionalOnSingleCandidate(spec, matchOutcome.getConditionMessage()); + if (!matchOutcome.isMatch()) { + return matchOutcome; } } if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { Spec spec = new Spec<>(context, metadata, annotations, ConditionalOnMissingBean.class); - MatchResult matchResult = getMatchingBeans(context, spec); - if (matchResult.isAnyMatched()) { - String reason = createOnMissingBeanNoMatchReason(matchResult); - return ConditionOutcome.noMatch(spec.message().because(reason)); + matchOutcome = evaluateConditionalOnMissingBean(spec, matchOutcome.getConditionMessage()); + if (!matchOutcome.isMatch()) { + return matchOutcome; } - matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll(); } - return ConditionOutcome.match(matchMessage); + return matchOutcome; + } + + private ConditionOutcome evaluateConditionalOnBean(Spec spec, ConditionMessage matchMessage) { + MatchResult matchResult = getMatchingBeans(spec); + if (!matchResult.isAllMatched()) { + String reason = createOnBeanNoMatchReason(matchResult); + return ConditionOutcome.noMatch(spec.message().because(reason)); + } + return ConditionOutcome.match(spec.message(matchMessage) + .found("bean", "beans") + .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); + } + + private ConditionOutcome evaluateConditionalOnSingleCandidate(Spec spec, + ConditionMessage matchMessage) { + MatchResult matchResult = getMatchingBeans(spec); + if (!matchResult.isAllMatched()) { + return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll()); + } + Set allBeans = matchResult.getNamesOfAllMatches(); + if (allBeans.size() == 1) { + return ConditionOutcome + .match(spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans)); + } + Map beanDefinitions = getBeanDefinitions(spec.context.getBeanFactory(), allBeans, + spec.getStrategy() == SearchStrategy.ALL); + List primaryBeans = getPrimaryBeans(beanDefinitions); + if (primaryBeans.size() == 1) { + return ConditionOutcome.match(spec.message(matchMessage) + .found("a single primary bean '" + primaryBeans.get(0) + "' from beans") + .items(Style.QUOTE, allBeans)); + } + if (primaryBeans.size() > 1) { + return ConditionOutcome + .noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans)); + } + List nonFallbackBeans = getNonFallbackBeans(beanDefinitions); + if (nonFallbackBeans.size() == 1) { + return ConditionOutcome.match(spec.message(matchMessage) + .found("a single non-fallback bean '" + nonFallbackBeans.get(0) + "' from beans") + .items(Style.QUOTE, allBeans)); + } + return ConditionOutcome.noMatch(spec.message().found("multiple beans").items(Style.QUOTE, allBeans)); + } + + private ConditionOutcome evaluateConditionalOnMissingBean(Spec spec, + ConditionMessage matchMessage) { + MatchResult matchResult = getMatchingBeans(spec); + if (matchResult.isAnyMatched()) { + String reason = createOnMissingBeanNoMatchReason(matchResult); + return ConditionOutcome.noMatch(spec.message().because(reason)); + } + return ConditionOutcome.match(spec.message(matchMessage).didNotFind("any beans").atAll()); } - protected final MatchResult getMatchingBeans(ConditionContext context, Spec spec) { - ClassLoader classLoader = context.getClassLoader(); - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + protected final MatchResult getMatchingBeans(Spec spec) { + ClassLoader classLoader = spec.getContext().getClassLoader(); + ConfigurableListableBeanFactory beanFactory = spec.getContext().getBeanFactory(); boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT; Set> parameterizedContainers = spec.getParameterizedContainers(); if (spec.getStrategy() == SearchStrategy.ANCESTORS) { @@ -180,26 +215,29 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec s Set beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers); for (String type : spec.getTypes()) { - Collection typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, - parameterizedContainers); - typeMatches - .removeIf((match) -> beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)); - if (typeMatches.isEmpty()) { + Map typeMatchedDefinitions = getBeanDefinitionsForType(classLoader, + considerHierarchy, beanFactory, type, parameterizedContainers); + Set typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions, + (name, definition) -> isCandidate(name, definition, beansIgnoredByType) + && !ScopedProxyUtils.isScopedTarget(name)); + if (typeMatchedNames.isEmpty()) { result.recordUnmatchedType(type); } else { - result.recordMatchedType(type, typeMatches); + result.recordMatchedType(type, typeMatchedNames); } } for (String annotation : spec.getAnnotations()) { - Set annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation, - considerHierarchy); - annotationMatches.removeAll(beansIgnoredByType); - if (annotationMatches.isEmpty()) { + Map annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader, + beanFactory, annotation, considerHierarchy); + Set annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions, + (name, definition) -> isCandidate(name, definition, beansIgnoredByType)); + if (annotationMatchedNames.isEmpty()) { result.recordUnmatchedAnnotation(annotation); } else { - result.recordMatchedAnnotation(annotation, annotationMatches); + result.recordMatchedAnnotation(annotation, annotationMatchedNames); + } } for (String beanName : spec.getNames()) { @@ -213,63 +251,88 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec s return result; } + private Set matchedNamesFrom(Map namedDefinitions, + BiPredicate filter) { + Set matchedNames = new LinkedHashSet<>(namedDefinitions.size()); + for (Entry namedDefinition : namedDefinitions.entrySet()) { + if (filter.test(namedDefinition.getKey(), namedDefinition.getValue())) { + matchedNames.add(namedDefinition.getKey()); + } + } + return matchedNames; + } + + private boolean isCandidate(String name, BeanDefinition definition, Set ignoredBeans) { + return (!ignoredBeans.contains(name)) + && (definition == null || (definition.isAutowireCandidate() && isDefaultCandidate(definition))); + } + + private boolean isDefaultCandidate(BeanDefinition definition) { + if (definition instanceof AbstractBeanDefinition abstractBeanDefinition) { + return abstractBeanDefinition.isDefaultCandidate(); + } + return true; + } + private Set getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory, boolean considerHierarchy, Set ignoredTypes, Set> parameterizedContainers) { Set result = null; for (String ignoredType : ignoredTypes) { - Collection ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, - ignoredType, parameterizedContainers); + Collection ignoredNames = getBeanDefinitionsForType(classLoader, considerHierarchy, beanFactory, + ignoredType, parameterizedContainers) + .keySet(); result = addAll(result, ignoredNames); } return (result != null) ? result : Collections.emptySet(); } - private Set getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy, + private Map getBeanDefinitionsForType(ClassLoader classLoader, boolean considerHierarchy, ListableBeanFactory beanFactory, String type, Set> parameterizedContainers) throws LinkageError { try { - return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader), + return getBeanDefinitionsForType(beanFactory, considerHierarchy, resolve(type, classLoader), parameterizedContainers); } catch (ClassNotFoundException | NoClassDefFoundError ex) { - return Collections.emptySet(); + return Collections.emptyMap(); } } - private Set getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class type, - Set> parameterizedContainers) { - Set result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers, - null); - return (result != null) ? result : Collections.emptySet(); + private Map getBeanDefinitionsForType(ListableBeanFactory beanFactory, + boolean considerHierarchy, Class type, Set> parameterizedContainers) { + Map result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, type, + parameterizedContainers, null); + return (result != null) ? result : Collections.emptyMap(); } - private Set collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, - Class type, Set> parameterizedContainers, Set result) { - result = addAll(result, beanFactory.getBeanNamesForType(type, true, false)); + private Map collectBeanDefinitionsForType(ListableBeanFactory beanFactory, + boolean considerHierarchy, Class type, Set> parameterizedContainers, + Map result) { + result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory); for (Class container : parameterizedContainers) { ResolvableType generic = ResolvableType.forClassWithGenerics(container, type); - result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false)); + result = putAll(result, beanFactory.getBeanNamesForType(generic, true, false), beanFactory); } if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory) { BeanFactory parent = hierarchicalBeanFactory.getParentBeanFactory(); if (parent instanceof ListableBeanFactory listableBeanFactory) { - result = collectBeanNamesForType(listableBeanFactory, considerHierarchy, type, parameterizedContainers, - result); + result = collectBeanDefinitionsForType(listableBeanFactory, considerHierarchy, type, + parameterizedContainers, result); } } return result; } - private Set getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory, - String type, boolean considerHierarchy) throws LinkageError { - Set result = null; + private Map getBeanDefinitionsForAnnotation(ClassLoader classLoader, + ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError { + Map result = null; try { - result = collectBeanNamesForAnnotation(beanFactory, resolveAnnotationType(classLoader, type), + result = collectBeanDefinitionsForAnnotation(beanFactory, resolveAnnotationType(classLoader, type), considerHierarchy, result); } catch (ClassNotFoundException ex) { // Continue } - return (result != null) ? result : Collections.emptySet(); + return (result != null) ? result : Collections.emptyMap(); } @SuppressWarnings("unchecked") @@ -278,13 +341,14 @@ private Class resolveAnnotationType(ClassLoader classLoade return (Class) resolve(type, classLoader); } - private Set collectBeanNamesForAnnotation(ListableBeanFactory beanFactory, - Class annotationType, boolean considerHierarchy, Set result) { - result = addAll(result, getBeanNamesForAnnotation(beanFactory, annotationType)); + private Map collectBeanDefinitionsForAnnotation(ListableBeanFactory beanFactory, + Class annotationType, boolean considerHierarchy, Map result) { + result = putAll(result, getBeanNamesForAnnotation(beanFactory, annotationType), beanFactory); if (considerHierarchy) { BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(); if (parent instanceof ListableBeanFactory listableBeanFactory) { - result = collectBeanNamesForAnnotation(listableBeanFactory, annotationType, considerHierarchy, result); + result = collectBeanDefinitionsForAnnotation(listableBeanFactory, annotationType, considerHierarchy, + result); } } return result; @@ -373,16 +437,32 @@ private void appendMessageForMatches(StringBuilder reason, Map getPrimaryBeans(ConfigurableListableBeanFactory beanFactory, Set beanNames, - boolean considerHierarchy) { - List primaryBeans = new ArrayList<>(); + private Map getBeanDefinitions(ConfigurableListableBeanFactory beanFactory, + Set beanNames, boolean considerHierarchy) { + Map definitions = new HashMap<>(beanNames.size()); for (String beanName : beanNames) { BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName, considerHierarchy); - if (beanDefinition != null && beanDefinition.isPrimary()) { - primaryBeans.add(beanName); + definitions.put(beanName, beanDefinition); + } + return definitions; + } + + private List getPrimaryBeans(Map beanDefinitions) { + return getMatchingBeans(beanDefinitions, BeanDefinition::isPrimary); + } + + private List getNonFallbackBeans(Map beanDefinitions) { + return getMatchingBeans(beanDefinitions, Predicate.not(BeanDefinition::isFallback)); + } + + private List getMatchingBeans(Map beanDefinitions, Predicate test) { + List matches = new ArrayList<>(); + for (Entry namedBeanDefinition : beanDefinitions.entrySet()) { + if (test.test(namedBeanDefinition.getValue())) { + matches.add(namedBeanDefinition.getKey()); } } - return primaryBeans; + return matches; } private BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName, @@ -406,12 +486,27 @@ private static Set addAll(Set result, Collection additio return result; } - private static Set addAll(Set result, String[] additional) { - if (ObjectUtils.isEmpty(additional)) { + private static Map putAll(Map result, String[] beanNames, + ListableBeanFactory beanFactory) { + if (ObjectUtils.isEmpty(beanNames)) { return result; } - result = (result != null) ? result : new LinkedHashSet<>(); - Collections.addAll(result, additional); + if (result == null) { + result = new LinkedHashMap<>(); + } + for (String beanName : beanNames) { + if (beanFactory instanceof ConfigurableListableBeanFactory clbf) { + try { + result.put(beanName, clbf.getBeanDefinition(beanName)); + } + catch (NoSuchBeanDefinitionException ex) { + result.put(beanName, null); + } + } + else { + result.put(beanName, null); + } + } return result; } @@ -420,7 +515,7 @@ private static Set addAll(Set result, String[] additional) { */ private static class Spec { - private final ClassLoader classLoader; + private final ConditionContext context; private final Class annotationType; @@ -442,7 +537,7 @@ private static class Spec { .filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)) .collect(MergedAnnotationCollectors.toMultiValueMap(Adapt.CLASS_TO_STRING)); MergedAnnotation annotation = annotations.get(annotationType); - this.classLoader = context.getClassLoader(); + this.context = context; this.annotationType = annotationType; this.names = extract(attributes, "name"); this.annotations = extract(attributes, "annotation"); @@ -451,7 +546,7 @@ private static class Spec { this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null); Set types = extractTypes(attributes); BeanTypeDeductionException deductionException = null; - if (types.isEmpty() && this.names.isEmpty()) { + if (types.isEmpty() && this.names.isEmpty() && this.annotations.isEmpty()) { try { types = deducedBeanType(context, metadata); } @@ -497,7 +592,7 @@ private Set> resolveWhenPossible(Set classNames) { Set> resolved = new LinkedHashSet<>(classNames.size()); for (String className : classNames) { try { - resolved.add(resolve(className, this.classLoader)); + resolved.add(resolve(className, this.context.getClassLoader())); } catch (ClassNotFoundException | NoClassDefFoundError ex) { // Ignore @@ -507,7 +602,7 @@ private Set> resolveWhenPossible(Set classNames) { } protected void validate(BeanTypeDeductionException ex) { - if (!hasAtLeastOneElement(this.types, this.names, this.annotations)) { + if (!hasAtLeastOneElement(getTypes(), getNames(), getAnnotations())) { String message = getAnnotationName() + " did not specify a bean using type, name or annotation"; if (ex == null) { throw new IllegalStateException(message); @@ -596,31 +691,35 @@ private SearchStrategy getStrategy() { return (this.strategy != null) ? this.strategy : SearchStrategy.ALL; } - Set getNames() { + private ConditionContext getContext() { + return this.context; + } + + private Set getNames() { return this.names; } - Set getTypes() { + protected Set getTypes() { return this.types; } - Set getAnnotations() { + private Set getAnnotations() { return this.annotations; } - Set getIgnoredTypes() { + private Set getIgnoredTypes() { return this.ignoredTypes; } - Set> getParameterizedContainers() { + private Set> getParameterizedContainers() { return this.parameterizedContainers; } - ConditionMessage.Builder message() { + private ConditionMessage.Builder message() { return ConditionMessage.forCondition(this.annotationType, this); } - ConditionMessage.Builder message(ConditionMessage message) { + private ConditionMessage.Builder message(ConditionMessage message) { return message.andCondition(this.annotationType, this); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java index dcb300c4b76e..9db6d9b1fd8b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.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. @@ -67,17 +67,18 @@ private ConditionOutcome getOutcome(String type) { return null; } ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class); + ClassNameFilter missingClassFilter = ClassNameFilter.MISSING; if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) { - if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) { + if (missingClassFilter.matches(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) { return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll()); } } if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) { - if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) { + if (missingClassFilter.matches(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) { return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll()); } } - if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader()) + if (missingClassFilter.matches(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader()) && !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) { return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll()); } @@ -123,7 +124,7 @@ private ConditionOutcome isAnyWebApplication(ConditionContext context, boolean r private ConditionOutcome isServletWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); - if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) { + if (ClassNameFilter.MISSING.matches(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) { return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll()); } if (context.getBeanFactory() != null) { @@ -143,7 +144,7 @@ private ConditionOutcome isServletWebApplication(ConditionContext context) { private ConditionOutcome isReactiveWebApplication(ConditionContext context) { ConditionMessage.Builder message = ConditionMessage.forCondition(""); - if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) { + if (ClassNameFilter.MISSING.matches(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) { return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll()); } if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/container/ContainerImageMetadata.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/container/ContainerImageMetadata.java new file mode 100644 index 000000000000..9a92d9c58c62 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/container/ContainerImageMetadata.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.autoconfigure.container; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.AttributeAccessor; + +/** + * Metadata about a container image that can be added to an {@link AttributeAccessor}. + * Primarily designed to be attached to {@link BeanDefinition BeanDefinitions} created in + * support of Testcontainers or Docker Compose. + * + * @param imageName the contaimer image name or {@code null} if the image name is not yet + * known + * @author Phillip Webb + * @since 3.4.0 + */ +public record ContainerImageMetadata(String imageName) { + + static final String NAME = ContainerImageMetadata.class.getName(); + + /** + * Add this container image metadata to the given attributes. + * @param attributes the attributes to add the metadata to + */ + public void addTo(AttributeAccessor attributes) { + if (attributes != null) { + attributes.setAttribute(NAME, this); + } + } + + /** + * Return {@code true} if {@link ContainerImageMetadata} has been added to the given + * attributes. + * @param attributes the attributes to check + * @return if metadata is present + */ + public static boolean isPresent(AttributeAccessor attributes) { + return getFrom(attributes) != null; + } + + /** + * Return {@link ContainerImageMetadata} from the given attributes or {@code null} if + * no metadata has been added. + * @param attributes the attributes + * @return the metadata or {@code null} + */ + public static ContainerImageMetadata getFrom(AttributeAccessor attributes) { + return (attributes != null) ? (ContainerImageMetadata) attributes.getAttribute(NAME) : null; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/container/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/container/package-info.java new file mode 100644 index 000000000000..0dda84287abf --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/container/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 classes related to auto-configuration involving containers. + */ +package org.springframework.boot.autoconfigure.container; 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 0ec92b6568b9..d1980b457d12 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 @@ package org.springframework.boot.autoconfigure.context; +import java.io.IOException; +import java.io.UncheckedIOException; import java.time.Duration; +import java.util.List; +import java.util.Properties; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -30,7 +34,6 @@ 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; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; @@ -39,10 +42,13 @@ import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.core.CollectionFactory; import org.springframework.core.Ordered; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.CollectionUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.StringUtils; @@ -53,30 +59,24 @@ * @author Phillip Webb * @author Eddú Meléndez * @author Marc Becker + * @author Misagh Moayyed * @since 1.5.0 */ @AutoConfiguration @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Conditional(ResourceBundleCondition.class) -@EnableConfigurationProperties +@EnableConfigurationProperties(MessageSourceProperties.class) @ImportRuntimeHints(MessageSourceRuntimeHints.class) public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; - @Bean - @ConfigurationProperties(prefix = "spring.messages") - public MessageSourceProperties messageSourceProperties() { - return new MessageSourceProperties(); - } - @Bean public MessageSource messageSource(MessageSourceProperties properties) { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); - if (StringUtils.hasText(properties.getBasename())) { - messageSource.setBasenames(StringUtils - .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); + if (!CollectionUtils.isEmpty(properties.getBasename())) { + messageSource.setBasenames(properties.getBasename().toArray(new String[0])); } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); @@ -88,9 +88,26 @@ public MessageSource messageSource(MessageSourceProperties properties) { } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); + messageSource.setCommonMessages(loadCommonMessages(properties.getCommonMessages())); return messageSource; } + private Properties loadCommonMessages(List resources) { + if (CollectionUtils.isEmpty(resources)) { + return null; + } + Properties properties = CollectionFactory.createSortedProperties(false); + for (Resource resource : resources) { + try { + PropertiesLoaderUtils.fillProperties(properties, resource); + } + catch (IOException ex) { + throw new UncheckedIOException("Failed to load common messages from '%s'".formatted(resource), ex); + } + } + return properties; + } + protected static class ResourceBundleCondition extends SpringBootCondition { private static final ConcurrentReferenceHashMap cache = new ConcurrentReferenceHashMap<>(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.java index fe5130a4f446..0495e285fd4f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.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,25 +20,36 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DurationUnit; +import org.springframework.core.io.Resource; /** * Configuration properties for Message Source. * * @author Stephane Nicoll * @author Kedar Joshi + * @author Misagh Moayyed * @since 2.0.0 */ +@ConfigurationProperties(prefix = "spring.messages") public class MessageSourceProperties { /** - * Comma-separated list of basenames (essentially a fully-qualified classpath - * location), each following the ResourceBundle convention with relaxed support for - * slash based locations. If it doesn't contain a package qualifier (such as - * "org.mypackage"), it will be resolved from the classpath root. + * List of basenames (essentially a fully-qualified classpath location), each + * following the ResourceBundle convention with relaxed support for slash based + * locations. If it doesn't contain a package qualifier (such as "org.mypackage"), it + * will be resolved from the classpath root. */ - private String basename = "messages"; + private List basename = new ArrayList<>(List.of("messages")); + + /** + * List of locale-independent property file resources containing common messages. + */ + private List commonMessages; /** * Message bundles encoding. @@ -71,11 +82,11 @@ public class MessageSourceProperties { */ private boolean useCodeAsDefaultMessage = false; - public String getBasename() { + public List getBasename() { return this.basename; } - public void setBasename(String basename) { + public void setBasename(List basename) { this.basename = basename; } @@ -119,4 +130,12 @@ public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) { this.useCodeAsDefaultMessage = useCodeAsDefaultMessage; } + public List getCommonMessages() { + return this.commonMessages; + } + + public void setCommonMessages(List commonMessages) { + this.commonMessages = commonMessages; + } + } 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..b935fbca3110 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,13 +16,16 @@ package org.springframework.boot.autoconfigure.couchbase; +import java.io.IOException; import java.io.InputStream; -import java.net.URL; +import java.security.GeneralSecurityException; import java.security.KeyStore; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import com.couchbase.client.core.env.Authenticator; +import com.couchbase.client.core.env.CertificateAuthenticator; +import com.couchbase.client.core.env.PasswordAuthenticator; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.ClusterOptions; import com.couchbase.client.java.codec.JacksonJsonSerializer; @@ -41,18 +44,24 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.CouchbaseCondition; +import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Authentication.Jks; +import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Authentication.Pem; import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Ssl; import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.io.ApplicationResourceLoader; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.ssl.pem.PemSslStore; +import org.springframework.boot.ssl.pem.PemSslStoreDetails; 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.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; -import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -64,6 +73,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick * @since 1.4.0 */ @AutoConfiguration(after = JacksonAutoConfiguration.class) @@ -72,9 +82,12 @@ @EnableConfigurationProperties(CouchbaseProperties.class) public class CouchbaseAutoConfiguration { + private final ResourceLoader resourceLoader; + private final CouchbaseProperties properties; - CouchbaseAutoConfiguration(CouchbaseProperties properties) { + CouchbaseAutoConfiguration(ResourceLoader resourceLoader, CouchbaseProperties properties) { + this.resourceLoader = ApplicationResourceLoader.get(resourceLoader); this.properties = properties; } @@ -86,25 +99,51 @@ PropertiesCouchbaseConnectionDetails couchbaseConnectionDetails() { @Bean @ConditionalOnMissingBean - public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseConnectionDetails connectionDetails, + public ClusterEnvironment couchbaseClusterEnvironment( ObjectProvider customizers, ObjectProvider sslBundles) { - Builder builder = initializeEnvironmentBuilder(connectionDetails, sslBundles.getIfAvailable()); + Builder builder = initializeEnvironmentBuilder(sslBundles.getIfAvailable()); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } + @Bean + @ConditionalOnMissingBean + public Authenticator couchbaseAuthenticator(CouchbaseConnectionDetails connectionDetails) throws IOException { + if (connectionDetails.getUsername() != null && connectionDetails.getPassword() != null) { + return PasswordAuthenticator.create(connectionDetails.getUsername(), connectionDetails.getPassword()); + } + Pem pem = this.properties.getAuthentication().getPem(); + if (pem.getCertificates() != null) { + PemSslStoreDetails details = new PemSslStoreDetails(null, pem.getCertificates(), pem.getPrivateKey()); + PemSslStore store = PemSslStore.load(details); + return CertificateAuthenticator.fromKey(store.privateKey(), pem.getPrivateKeyPassword(), + store.certificates()); + } + Jks jks = this.properties.getAuthentication().getJks(); + if (jks.getLocation() != null) { + Resource resource = this.resourceLoader.getResource(jks.getLocation()); + String keystorePassword = jks.getPassword(); + try (InputStream inputStream = resource.getInputStream()) { + KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); + store.load(inputStream, (keystorePassword != null) ? keystorePassword.toCharArray() : null); + return CertificateAuthenticator.fromKeyStore(store, keystorePassword); + } + catch (GeneralSecurityException ex) { + throw new IllegalStateException("Error reading Couchbase certificate store", ex); + } + } + throw new IllegalStateException("Couchbase authentication requires username and password, or certificates"); + } + @Bean(destroyMethod = "disconnect") @ConditionalOnMissingBean - public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment, + public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment, Authenticator authenticator, CouchbaseConnectionDetails connectionDetails) { - ClusterOptions options = ClusterOptions - .clusterOptions(connectionDetails.getUsername(), connectionDetails.getPassword()) - .environment(couchbaseClusterEnvironment); + ClusterOptions options = ClusterOptions.clusterOptions(authenticator).environment(couchbaseClusterEnvironment); return Cluster.connect(connectionDetails.getConnectionString(), options); } - private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseConnectionDetails connectionDetails, - SslBundles sslBundles) { + private ClusterEnvironment.Builder initializeEnvironmentBuilder(SslBundles sslBundles) { ClusterEnvironment.Builder builder = ClusterEnvironment.builder(); Timeouts timeouts = this.properties.getEnv().getTimeouts(); builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue()) @@ -134,45 +173,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 d1e93181c570..2f4d2e8a0d05 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; /** @@ -30,6 +29,7 @@ * @author Yulin Qin * @author Brian Clozel * @author Michael Nitschinger + * @author Scott Frederick * @since 1.4.0 */ @ConfigurationProperties(prefix = "spring.couchbase") @@ -50,6 +50,8 @@ public class CouchbaseProperties { */ private String password; + private final Authentication authentication = new Authentication(); + private final Env env = new Env(); public String getConnectionString() { @@ -76,10 +78,116 @@ public void setPassword(String password) { this.password = password; } + public Authentication getAuthentication() { + return this.authentication; + } + public Env getEnv() { return this.env; } + public static class Authentication { + + private final Pem pem = new Pem(); + + private final Jks jks = new Jks(); + + public Pem getPem() { + return this.pem; + } + + public Jks getJks() { + return this.jks; + } + + public static class Pem { + + /** + * PEM-formatted certificates for certificate-based cluster authentication. + */ + private String certificates; + + /** + * PEM-formatted private key for certificate-based cluster authentication. + */ + private String privateKey; + + /** + * Private key password for certificate-based cluster authentication. + */ + private String privateKeyPassword; + + public String getCertificates() { + return this.certificates; + } + + public void setCertificates(String certificates) { + this.certificates = certificates; + } + + public String getPrivateKey() { + return this.privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public String getPrivateKeyPassword() { + return this.privateKeyPassword; + } + + public void setPrivateKeyPassword(String privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; + } + + } + + public static class Jks { + + /** + * Java KeyStore location for certificate-based cluster authentication. + */ + private String location; + + /** + * Java KeyStore password for certificate-based cluster authentication. + */ + private String password; + + /** + * Private key password for certificate-based cluster authentication. + */ + private String privateKeyPassword; + + public String getLocation() { + return this.location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPrivateKeyPassword() { + return this.privateKeyPassword; + } + + public void setPrivateKeyPassword(String privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; + } + + } + + } + public static class Env { private final Io io = new Io(); @@ -149,61 +257,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", - since = "3.1.0") - 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", - since = "3.1.0") - 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/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/neo4j/Neo4jDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java index 122b36372868..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 @@ -113,7 +113,7 @@ public Neo4jTemplate neo4jTemplate(Neo4jClient neo4jClient, Neo4jMappingContext public Neo4jTransactionManager transactionManager(Driver driver, DatabaseSelectionProvider databaseNameProvider, ObjectProvider optionalCustomizers) { Neo4jTransactionManager transactionManager = new Neo4jTransactionManager(driver, databaseNameProvider); - optionalCustomizers.ifAvailable((customizer) -> customizer.customize((TransactionManager) transactionManager)); + optionalCustomizers.ifAvailable((customizer) -> customizer.customize(transactionManager)); return transactionManager; } 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 c987df24b8e7..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 @@ -88,18 +88,22 @@ DefaultClientResources lettuceClientResources(ObjectProvider builderCustomizers, + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources) { - return createConnectionFactory(builderCustomizers, clientResources); + return createConnectionFactory(clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, + clientResources); } @Bean @ConditionalOnMissingBean(RedisConnectionFactory.class) @ConditionalOnThreading(Threading.VIRTUAL) LettuceConnectionFactory redisConnectionFactoryVirtualThreads( - ObjectProvider builderCustomizers, + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources) { - LettuceConnectionFactory factory = createConnectionFactory(builderCustomizers, clientResources); + LettuceConnectionFactory factory = createConnectionFactory(clientConfigurationBuilderCustomizers, + clientOptionsBuilderCustomizers, clientResources); SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-"); executor.setVirtualThreads(true); factory.setExecutor(executor); @@ -107,10 +111,11 @@ LettuceConnectionFactory redisConnectionFactoryVirtualThreads( } private LettuceConnectionFactory createConnectionFactory( - ObjectProvider builderCustomizers, + ObjectProvider clientConfigurationBuilderCustomizers, + ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources) { - LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources, - getProperties().getLettuce().getPool()); + LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientConfigurationBuilderCustomizers, + clientOptionsBuilderCustomizers, clientResources, getProperties().getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig); } @@ -125,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(); } @@ -163,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) { @@ -183,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/RedisProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index 91a58ac42fe9..8e5f07eb5bf3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -322,8 +322,8 @@ public void setTimeBetweenEvictionRuns(Duration timeBetweenEvictionRuns) { public static class Cluster { /** - * Comma-separated list of "host:port" pairs to bootstrap from. This represents an - * "initial" list of cluster nodes and is required to have at least one entry. + * List of "host:port" pairs to bootstrap from. This represents an "initial" list + * of cluster nodes and is required to have at least one entry. */ private List nodes; @@ -362,7 +362,7 @@ public static class Sentinel { private String master; /** - * Comma-separated list of "host:port" pairs. + * List of "host:port" pairs. */ private List nodes; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.java index 584ec7010daf..1193e0eeaee3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.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 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.data.web.config.PageableHandlerMethodArgumentResolverCustomizer; import org.springframework.data.web.config.SortHandlerMethodArgumentResolverCustomizer; +import org.springframework.data.web.config.SpringDataWebSettings; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** @@ -42,6 +43,7 @@ * * @author Andy Wilkinson * @author Vedran Pavic + * @author Yanming Zhou * @since 1.2.0 */ @AutoConfiguration(after = RepositoryRestMvcAutoConfiguration.class) @@ -79,4 +81,10 @@ public SortHandlerMethodArgumentResolverCustomizer sortCustomizer() { return (resolver) -> resolver.setSortParameter(this.properties.getSort().getSortParameter()); } + @Bean + @ConditionalOnMissingBean + public SpringDataWebSettings springDataWebSettings() { + return new SpringDataWebSettings(this.properties.getPageable().getSerializationMode()); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebProperties.java index a736f404036e..fd59dc60748f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebProperties.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. @@ -17,11 +17,13 @@ package org.springframework.boot.autoconfigure.data.web; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode; /** * Configuration properties for Spring Data Web. * * @author Vedran Pavic + * @author Yanming Zhou * @since 2.0.0 */ @ConfigurationProperties("spring.data.web") @@ -81,6 +83,11 @@ public static class Pageable { */ private int maxPageSize = 2000; + /** + * Configures how to render Spring Data Pageable instances. + */ + private PageSerializationMode serializationMode = PageSerializationMode.DIRECT; + public String getPageParameter() { return this.pageParameter; } @@ -137,6 +144,14 @@ public void setMaxPageSize(int maxPageSize) { this.maxPageSize = maxPageSize; } + public PageSerializationMode getSerializationMode() { + return this.serializationMode; + } + + public void setSerializationMode(PageSerializationMode serializationMode) { + this.serializationMode = serializationMode; + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java index d2ab6e88a0ed..82c0ce9178f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/domain/EntityScanPackages.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; @@ -159,7 +159,7 @@ private Set getPackagesToScan(AnnotationMetadata metadata) { } - static class EntityScanPackagesBeanDefinition extends GenericBeanDefinition { + static class EntityScanPackagesBeanDefinition extends RootBeanDefinition { private final Set packageNames = new LinkedHashSet<>(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java index f9c15eaa99b8..a0325023aeb3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ public class ElasticsearchProperties { /** - * Comma-separated list of the Elasticsearch instances to use. + * List of the Elasticsearch instances to use. */ private List uris = new ArrayList<>(Collections.singletonList("http://localhost:9200")); 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 34d64312f6ec..d07ceeef606e 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 @@ -35,12 +35,10 @@ import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.extensibility.ConfigurationExtension; -import org.flywaydb.core.internal.database.postgresql.PostgreSQLConfigurationExtension; -import org.flywaydb.core.internal.scanner.Scanner; import org.flywaydb.database.oracle.OracleConfigurationExtension; +import org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.ObjectProvider; @@ -80,7 +78,6 @@ import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -160,7 +157,7 @@ OracleFlywayConfigurationCustomizer oracleFlywayConfigurationCustomizer() { } @Bean - @ConditionalOnClass(name = "org.flywaydb.core.internal.database.postgresql.PostgreSQLConfigurationExtension") + @ConditionalOnClass(name = "org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension") PostgresqlFlywayConfigurationCustomizer postgresqlFlywayConfigurationCustomizer() { return new PostgresqlFlywayConfigurationCustomizer(this.properties); } @@ -228,6 +225,7 @@ private void applyConnectionDetails(FlywayConnectionDetails connectionDetails, D * @param configuration the configuration * @param properties the properties */ + @SuppressWarnings("removal") private void configureProperties(FluentConfiguration configuration, FlywayProperties properties) { // NOTE: Using method references in the mapper methods can break // back-compatibility (see gh-38164) @@ -305,16 +303,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.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)); @@ -466,9 +462,6 @@ static class FlywayAutoConfigurationRuntimeHints implements RuntimeHintsRegistra @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { hints.resources().registerPattern("db/migration/*"); - if (ClassUtils.isPresent("org.flywaydb.core.extensibility.Tier", classLoader)) { - hints.reflection().registerType(Scanner.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - } } } 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 4a6d7db6e0ca..e65b1ad515f7 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. @@ -291,27 +291,11 @@ public class FlywayProperties { */ private String[] errorOverrides; - /** - * Licence key for Flyway Teams. - */ - private String licenseKey; - /** * 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. */ @@ -335,8 +319,8 @@ public class FlywayProperties { private Boolean skipExecutingMigrations; /** - * Ignore migrations that match this comma-separated list of patterns when validating - * migrations. Requires Flyway Teams. + * List of patterns that identify migrations to ignore when performing validation. + * Requires Flyway Teams. */ private List ignoreMigrationPatterns; @@ -346,6 +330,11 @@ 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(); @@ -608,10 +597,13 @@ public void setCleanDisabled(boolean cleanDisabled) { this.cleanDisabled = cleanDisabled; } + @Deprecated(since = "3.4.0", forRemoval = true) + @DeprecatedConfigurationProperty(since = "3.4.0", reason = "Deprecated in Flyway 10.18") public boolean isCleanOnValidationError() { return this.cleanOnValidationError; } + @Deprecated(since = "3.4.0", forRemoval = true) public void setCleanOnValidationError(boolean cleanOnValidationError) { this.cleanOnValidationError = cleanOnValidationError; } @@ -728,14 +720,6 @@ 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() { @@ -777,22 +761,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; } @@ -863,6 +831,14 @@ 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; } 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 c9e3006b911c..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 @@ -16,10 +16,8 @@ package org.springframework.boot.autoconfigure.flyway; -import java.lang.reflect.Constructor; import java.util.Arrays; -import org.flywaydb.core.api.configuration.Configuration; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.internal.scanner.LocationScannerCache; @@ -31,14 +29,14 @@ * {@link org.flywaydb.core.api.ResourceProvider}. * * @author Moritz Halbritter - * @author Maziz Esa */ class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer { @Override public void customize(FluentConfiguration configuration) { if (configuration.getResourceProvider() == null) { - Scanner scanner = createScanner(configuration); + 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()); @@ -46,28 +44,4 @@ public void customize(FluentConfiguration configuration) { } } - private static Scanner createScanner(FluentConfiguration configuration) { - try { - return new Scanner<>(JavaMigration.class, Arrays.asList(configuration.getLocations()), - configuration.getClassLoader(), configuration.getEncoding(), configuration.isDetectEncoding(), - false, new ResourceNameCache(), new LocationScannerCache(), - configuration.isFailOnMissingLocations()); - } - catch (NoSuchMethodError ex) { - return createFlyway10Scanner(configuration); - } - } - - private static Scanner createFlyway10Scanner(FluentConfiguration configuration) throws LinkageError { - try { - Constructor constructor = Scanner.class.getDeclaredConstructor(Class.class, boolean.class, - ResourceNameCache.class, LocationScannerCache.class, Configuration.class); - return (Scanner) constructor.newInstance(JavaMigration.class, false, new ResourceNameCache(), - new LocationScannerCache(), configuration); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/AbstractFreeMarkerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/AbstractFreeMarkerConfiguration.java index 0f4def064dcb..ba352f78a77e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/AbstractFreeMarkerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/AbstractFreeMarkerConfiguration.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,21 +16,30 @@ package org.springframework.boot.autoconfigure.freemarker; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory; /** * Base class for shared FreeMarker configuration. * * @author Brian Clozel + * @author Stephane Nicoll */ abstract class AbstractFreeMarkerConfiguration { private final FreeMarkerProperties properties; - protected AbstractFreeMarkerConfiguration(FreeMarkerProperties properties) { + private final List variablesCustomizers; + + protected AbstractFreeMarkerConfiguration(FreeMarkerProperties properties, + ObjectProvider variablesCustomizers) { this.properties = properties; + this.variablesCustomizers = variablesCustomizers.orderedStream().toList(); } protected final FreeMarkerProperties getProperties() { @@ -41,10 +50,23 @@ protected void applyProperties(FreeMarkerConfigurationFactory factory) { factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath()); factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess()); factory.setDefaultEncoding(this.properties.getCharsetName()); + factory.setFreemarkerSettings(createFreeMarkerSettings()); + factory.setFreemarkerVariables(createFreeMarkerVariables()); + } + + private Properties createFreeMarkerSettings() { Properties settings = new Properties(); settings.put("recognize_standard_file_extensions", "true"); settings.putAll(this.properties.getSettings()); - factory.setFreemarkerSettings(settings); + return settings; + } + + private Map createFreeMarkerVariables() { + Map variables = new HashMap<>(); + for (FreeMarkerVariablesCustomizer customizer : this.variablesCustomizers) { + customizer.customizeFreeMarkerVariables(variables); + } + return variables; } } 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/freemarker/FreeMarkerNonWebConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerNonWebConfiguration.java index ad362826afb1..89d572c87783 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerNonWebConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerNonWebConfiguration.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,7 @@ package org.springframework.boot.autoconfigure.freemarker; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; import org.springframework.context.annotation.Bean; @@ -32,8 +33,9 @@ @ConditionalOnNotWebApplication class FreeMarkerNonWebConfiguration extends AbstractFreeMarkerConfiguration { - FreeMarkerNonWebConfiguration(FreeMarkerProperties properties) { - super(properties); + FreeMarkerNonWebConfiguration(FreeMarkerProperties properties, + ObjectProvider variablesCustomizers) { + super(properties, variablesCustomizers); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerProperties.java index 6e6087d57fd5..e25c7da057e4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerProperties.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. @@ -44,7 +44,7 @@ public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties private Map settings = new HashMap<>(); /** - * Comma-separated list of template paths. + * List of template paths. */ private String[] templateLoaderPath = new String[] { DEFAULT_TEMPLATE_LOADER_PATH }; @@ -72,6 +72,10 @@ public String[] getTemplateLoaderPath() { return this.templateLoaderPath; } + public void setTemplateLoaderPath(String... templateLoaderPaths) { + this.templateLoaderPath = templateLoaderPaths; + } + public boolean isPreferFileSystemAccess() { return this.preferFileSystemAccess; } @@ -80,8 +84,4 @@ public void setPreferFileSystemAccess(boolean preferFileSystemAccess) { this.preferFileSystemAccess = preferFileSystemAccess; } - public void setTemplateLoaderPath(String... templateLoaderPaths) { - this.templateLoaderPath = templateLoaderPaths; - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerReactiveWebConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerReactiveWebConfiguration.java index a381f0496a9b..383fc1fd65bc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerReactiveWebConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerReactiveWebConfiguration.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,7 @@ package org.springframework.boot.autoconfigure.freemarker; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -38,8 +39,9 @@ @AutoConfigureAfter(WebFluxAutoConfiguration.class) class FreeMarkerReactiveWebConfiguration extends AbstractFreeMarkerConfiguration { - FreeMarkerReactiveWebConfiguration(FreeMarkerProperties properties) { - super(properties); + FreeMarkerReactiveWebConfiguration(FreeMarkerProperties properties, + ObjectProvider variablesCustomizers) { + super(properties, variablesCustomizers); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerServletWebConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerServletWebConfiguration.java index adf878e6b10b..e268c57cf740 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerServletWebConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerServletWebConfiguration.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.servlet.DispatcherType; import jakarta.servlet.Servlet; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -47,8 +48,9 @@ @AutoConfigureAfter(WebMvcAutoConfiguration.class) class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration { - protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties) { - super(properties); + protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties, + ObjectProvider variablesCustomizers) { + super(properties, variablesCustomizers); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerVariablesCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerVariablesCustomizer.java new file mode 100644 index 000000000000..7d072d63e003 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerVariablesCustomizer.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.freemarker; + +import java.util.Map; + +import freemarker.template.Configuration; + +import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory; + +/** + * Callback interface that can be implemented by beans wishing to customize the FreeMarker + * variables used as {@link Configuration#getSharedVariableNames() shared variables} + * before it is used by an auto-configured {@link FreeMarkerConfigurationFactory}. + * + * @author Stephane Nicoll + * @since 3.4.0 + */ +@FunctionalInterface +public interface FreeMarkerVariablesCustomizer { + + /** + * Customize the {@code variables} to be set as well-known FreeMarker objects. + * @param variables the variables to customize + * @see FreeMarkerConfigurationFactory#setFreemarkerVariables(Map) + */ + void customizeFreeMarkerVariables(Map variables); + +} 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 9b9c408d7a58..599b613ad4a5 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 @@ -49,6 +49,7 @@ import org.springframework.core.log.LogMessage; import org.springframework.data.domain.ScrollPosition; import org.springframework.graphql.ExecutionGraphQlService; +import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; import org.springframework.graphql.data.pagination.ConnectionFieldTypeVisitor; import org.springframework.graphql.data.pagination.CursorEncoder; @@ -154,11 +155,13 @@ public ExecutionGraphQlService executionGraphQlService(GraphQlSource graphQlSour @Bean @ConditionalOnMissingBean public AnnotatedControllerConfigurer annotatedControllerConfigurer( - @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) ObjectProvider executorProvider) { + @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) ObjectProvider executorProvider, + ObjectProvider argumentResolvers) { AnnotatedControllerConfigurer controllerConfigurer = new AnnotatedControllerConfigurer(); controllerConfigurer .addFormatterRegistrar((registry) -> ApplicationConversionService.addBeans(registry, this.beanFactory)); executorProvider.ifAvailable(controllerConfigurer::setExecutor); + argumentResolvers.orderedStream().forEach(controllerConfigurer::addCustomArgumentResolver); return controllerConfigurer; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java index 7611f4e3eeb7..ee9dbc1896a0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlCorsProperties.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. @@ -38,34 +38,34 @@ public class GraphQlCorsProperties { /** - * Comma-separated list of origins to allow with '*' allowing all origins. When - * allow-credentials is enabled, '*' cannot be used, and setting origin patterns - * should be considered instead. When neither allowed origins nor allowed origin - * patterns are set, cross-origin requests are effectively disabled. + * List of origins to allow with '*' allowing all origins. When allow-credentials is + * enabled, '*' cannot be used, and setting origin patterns should be considered + * instead. When neither allowed origins nor allowed origin patterns are set, + * cross-origin requests are effectively disabled. */ private List allowedOrigins = new ArrayList<>(); /** - * Comma-separated list of origin patterns to allow. Unlike allowed origins which only - * support '*', origin patterns are more flexible, e.g. 'https://*.example.com', and - * can be used with allow-credentials. When neither allowed origins nor allowed origin - * patterns are set, cross-origin requests are effectively disabled. + * List of origin patterns to allow. Unlike allowed origins which only support '*', + * origin patterns are more flexible, e.g. 'https://*.example.com', and can be used + * with allow-credentials. When neither allowed origins nor allowed origin patterns + * are set, cross-origin requests are effectively disabled. */ private List allowedOriginPatterns = new ArrayList<>(); /** - * Comma-separated list of HTTP methods to allow. '*' allows all methods. When not - * set, defaults to GET. + * List of HTTP methods to allow. '*' allows all methods. When not set, defaults to + * GET. */ private List allowedMethods = new ArrayList<>(); /** - * Comma-separated list of HTTP headers to allow in a request. '*' allows all headers. + * List of HTTP headers to allow in a request. '*' allows all headers. */ private List allowedHeaders = new ArrayList<>(); /** - * Comma-separated list of headers to include in a response. + * List of headers to include in a response. */ private List exposedHeaders = new ArrayList<>(); 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 046155b1aa4f..b39692f83f07 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. @@ -217,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; + public String getPath() { return this.path; } @@ -233,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/reactive/GraphQlWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java index 70fd6f1d154e..05f6eaadcf1c 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,6 +47,8 @@ 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; @@ -59,7 +61,6 @@ 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 +68,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 +83,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,19 +100,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.POST(path, SUPPORTS_MEDIATYPES, httpHandler::handleRequest); + builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); + builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest); + builder.POST(path, this::unsupportedMediaType); builder.GET(path, this::onlyAllowPost); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphQlHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); @@ -125,6 +126,14 @@ public RouterFunction graphQlRouterFunction(GraphQlHttpHandler h return builder.build(); } + private Mono unsupportedMediaType(ServerRequest request) { + return ServerResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).headers(this::acceptJson).build(); + } + + private void acceptJson(HttpHeaders headers) { + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + } + private Mono onlyAllowPost(ServerRequest request) { return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build(); } @@ -164,7 +173,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 2b374a541bb2..c36823761e8e 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,12 +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.POST(path, RequestPredicates.contentType(MediaType.APPLICATION_JSON) - .and(RequestPredicates.accept(SUPPORTED_MEDIA_TYPES)), httpHandler::handleRequest); + builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest); + builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest); + builder.POST(path, this::unsupportedMediaType); builder.GET(path, this::onlyAllowPost); if (properties.getGraphiql().isEnabled()) { GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath()); @@ -126,6 +130,14 @@ public RouterFunction graphQlRouterFunction(GraphQlHttpHandler h return builder.build(); } + private ServerResponse unsupportedMediaType(ServerRequest request) { + return ServerResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).headers(this::acceptJson).build(); + } + + private void acceptJson(HttpHeaders headers) { + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + } + private ServerResponse onlyAllowPost(ServerRequest request) { return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).headers(this::onlyAllowPost).build(); } @@ -166,7 +178,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) { 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 94ed69c74517..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,7 +92,7 @@ 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::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/hazelcast/HazelcastClientConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java index 570c7a51a25e..5c6c0585ab0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.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,24 +16,13 @@ package org.springframework.boot.autoconfigure.hazelcast; -import java.io.IOException; -import java.net.URL; - import com.hazelcast.client.HazelcastClient; -import com.hazelcast.client.config.ClientConfig; -import com.hazelcast.client.config.XmlClientConfigBuilder; -import com.hazelcast.client.config.YamlClientConfigBuilder; import com.hazelcast.core.HazelcastInstance; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.util.StringUtils; +import org.springframework.context.annotation.Import; /** * Configuration for Hazelcast client. @@ -44,51 +33,9 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HazelcastClient.class) @ConditionalOnMissingBean(HazelcastInstance.class) +@Import({ HazelcastConnectionDetailsConfiguration.class, HazelcastClientInstanceConfiguration.class }) class HazelcastClientConfiguration { static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.client.config"; - private static HazelcastInstance getHazelcastInstance(ClientConfig config) { - if (StringUtils.hasText(config.getInstanceName())) { - return HazelcastClient.getOrCreateHazelcastClient(config); - } - return HazelcastClient.newHazelcastClient(config); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(ClientConfig.class) - @Conditional(HazelcastClientConfigAvailableCondition.class) - static class HazelcastClientConfigFileConfiguration { - - @Bean - HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader) - throws IOException { - Resource configLocation = properties.resolveConfigLocation(); - ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load(); - config.setClassLoader(resourceLoader.getClassLoader()); - return getHazelcastInstance(config); - } - - private ClientConfig loadClientConfig(Resource configLocation) throws IOException { - URL configUrl = configLocation.getURL(); - String configFileName = configUrl.getPath(); - if (configFileName.endsWith(".yaml") || configFileName.endsWith(".yml")) { - return new YamlClientConfigBuilder(configUrl).build(); - } - return new XmlClientConfigBuilder(configUrl).build(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnSingleCandidate(ClientConfig.class) - static class HazelcastClientConfigConfiguration { - - @Bean - HazelcastInstance hazelcastInstance(ClientConfig config) { - return getHazelcastInstance(config); - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java new file mode 100644 index 000000000000..824967cd79d0 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.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.hazelcast; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +/** + * Configuration for Hazelcast client instance. + * + * @author Dmytro Nosan + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnBean(HazelcastConnectionDetails.class) +class HazelcastClientInstanceConfiguration { + + @Bean + HazelcastInstance hazelcastInstance(HazelcastConnectionDetails hazelcastConnectionDetails) { + ClientConfig config = hazelcastConnectionDetails.getClientConfig(); + return (!StringUtils.hasText(config.getInstanceName())) ? HazelcastClient.newHazelcastClient(config) + : HazelcastClient.getOrCreateHazelcastClient(config); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java new file mode 100644 index 000000000000..7b98f817d6c6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.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.hazelcast; + +import com.hazelcast.client.config.ClientConfig; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a client connection to a Hazelcast instance. + * + * @author Dmytro Nosan + * @since 3.4.0 + */ +public interface HazelcastConnectionDetails extends ConnectionDetails { + + /** + * The {@link ClientConfig} for Hazelcast client. + * @return the client config + */ + ClientConfig getClientConfig(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetailsConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetailsConfiguration.java new file mode 100644 index 000000000000..797da5fbbd25 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetailsConfiguration.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.hazelcast; + +import com.hazelcast.client.config.ClientConfig; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; + +/** + * {@link Configuration} for providing {@link HazelcastConnectionDetails}. + * + * @author Dmytro Nosan + * @author Moritz Halbritter + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnMissingBean(HazelcastConnectionDetails.class) +class HazelcastConnectionDetailsConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(ClientConfig.class) + @Conditional(HazelcastClientConfigAvailableCondition.class) + static class HazelcastClientConfigFileConfiguration { + + @Bean + HazelcastConnectionDetails hazelcastConnectionDetails(HazelcastProperties properties, + ResourceLoader resourceLoader) { + return new PropertiesHazelcastConnectionDetails(properties, resourceLoader); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnSingleCandidate(ClientConfig.class) + static class HazelcastClientConfigConfiguration { + + @Bean + HazelcastConnectionDetails hazelcastConnectionDetails(ClientConfig config) { + return () -> config; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java new file mode 100644 index 000000000000..8f20e236a771 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.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.autoconfigure.hazelcast; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.Locale; + +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.client.config.XmlClientConfigBuilder; +import com.hazelcast.client.config.YamlClientConfigBuilder; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * Adapts {@link HazelcastProperties} to {@link HazelcastConnectionDetails}. + * + * @author Dmytro Nosan + */ +class PropertiesHazelcastConnectionDetails implements HazelcastConnectionDetails { + + private final HazelcastProperties properties; + + private final ResourceLoader resourceLoader; + + PropertiesHazelcastConnectionDetails(HazelcastProperties properties, ResourceLoader resourceLoader) { + this.properties = properties; + this.resourceLoader = resourceLoader; + } + + @Override + public ClientConfig getClientConfig() { + Resource configLocation = this.properties.resolveConfigLocation(); + ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load(); + config.setClassLoader(this.resourceLoader.getClassLoader()); + return config; + } + + private ClientConfig loadClientConfig(Resource configLocation) { + try { + URL configUrl = configLocation.getURL(); + String configFileName = configUrl.getPath().toLowerCase(Locale.ROOT); + return (!isYaml(configFileName)) ? new XmlClientConfigBuilder(configUrl).build() + : new YamlClientConfigBuilder(configUrl).build(); + } + catch (IOException ex) { + throw new UncheckedIOException("Failed to load Hazelcast config", ex); + } + } + + private boolean isYaml(String configFileName) { + return configFileName.endsWith(".yaml") || configFileName.endsWith(".yml"); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java new file mode 100644 index 000000000000..faa6273442db --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.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.autoconfigure.http.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.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.client.HttpClientProperties.Factory; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.StringUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for + * {@link ClientHttpRequestFactoryBuilder} and {@link ClientHttpRequestFactorySettings}. + * + * @author Phillip Webb + * @since 3.4.0 + */ +@AutoConfiguration(after = SslAutoConfiguration.class) +@ConditionalOnClass(ClientHttpRequestFactory.class) +@Conditional(NotReactiveWebApplicationCondition.class) +@EnableConfigurationProperties(HttpClientProperties.class) +public class HttpClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder(HttpClientProperties httpClientProperties) { + Factory factory = httpClientProperties.getFactory(); + return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect(); + } + + @Bean + @ConditionalOnMissingBean + ClientHttpRequestFactorySettings clientHttpRequestFactorySettings(HttpClientProperties httpClientProperties, + ObjectProvider sslBundles) { + SslBundle sslBundle = getSslBundle(httpClientProperties.getSsl(), sslBundles); + return new ClientHttpRequestFactorySettings(httpClientProperties.getRedirects(), + httpClientProperties.getConnectTimeout(), httpClientProperties.getReadTimeout(), sslBundle); + } + + private SslBundle getSslBundle(HttpClientProperties.Ssl properties, ObjectProvider sslBundles) { + String name = properties.getBundle(); + return (StringUtils.hasLength(name)) ? sslBundles.getObject().getBundle(name) : null; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientProperties.java new file mode 100644 index 000000000000..abdc2e0c4c13 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientProperties.java @@ -0,0 +1,159 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.http.client; + +import java.time.Duration; +import java.util.function.Supplier; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects; + +/** + * {@link ConfigurationProperties @ConfigurationProperties} for a Spring's blocking HTTP + * clients. + * + * @author Phillip Webb + * @since 3.4.0 + */ +@ConfigurationProperties("spring.http.client") +public class HttpClientProperties { + + /** + * Default factory used for a client HTTP request. + */ + private Factory factory; + + /** + * Handling for HTTP redirects. + */ + private Redirects redirects = Redirects.FOLLOW_WHEN_POSSIBLE; + + /** + * Default connect timeout for a client HTTP request. + */ + private Duration connectTimeout; + + /** + * Default read timeout for a client HTTP request. + */ + private Duration readTimeout; + + /** + * Default SSL configuration for a client HTTP request. + */ + private final Ssl ssl = new Ssl(); + + public Factory getFactory() { + return this.factory; + } + + public void setFactory(Factory factory) { + this.factory = factory; + } + + public Redirects getRedirects() { + return this.redirects; + } + + public void setRedirects(Redirects redirects) { + this.redirects = redirects; + } + + public Duration getConnectTimeout() { + return this.connectTimeout; + } + + public void setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public Duration getReadTimeout() { + return this.readTimeout; + } + + public void setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + } + + public Ssl getSsl() { + return this.ssl; + } + + /** + * Supported factory types. + */ + public enum Factory { + + /** + * Apache HttpComponents HttpClient. + */ + HTTP_COMPONENTS(ClientHttpRequestFactoryBuilder::httpComponents), + + /** + * Jetty's HttpClient. + */ + JETTY(ClientHttpRequestFactoryBuilder::jetty), + + /** + * Reactor-Netty. + */ + REACTOR(ClientHttpRequestFactoryBuilder::reactor), + + /** + * Java's HttpClient. + */ + JDK(ClientHttpRequestFactoryBuilder::jdk), + + /** + * Standard JDK facilities. + */ + SIMPLE(ClientHttpRequestFactoryBuilder::simple); + + private final Supplier> builderSupplier; + + Factory(Supplier> builderSupplier) { + this.builderSupplier = builderSupplier; + } + + ClientHttpRequestFactoryBuilder builder() { + return this.builderSupplier.get(); + } + + } + + /** + * SSL configuration. + */ + public static class Ssl { + + /** + * SSL bundle to use. + */ + private String bundle; + + 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/http/client/NotReactiveWebApplicationCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/NotReactiveWebApplicationCondition.java new file mode 100644 index 000000000000..1d7b8080c201 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/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.http.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/http/client/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/package-info.java new file mode 100644 index 000000000000..b5a8eac3f27e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/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 client-side HTTP. + */ +package org.springframework.boot.autoconfigure.http.client; 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 904541755a4b..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfiguration.java +++ /dev/null @@ -1,71 +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 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the - * new client and its own - * Spring Boot integration. - */ -@AutoConfiguration -@ConditionalOnClass(InfluxDB.class) -@EnableConfigurationProperties(InfluxDbProperties.class) -@ConditionalOnProperty("spring.influx.url") -@Deprecated(since = "3.2.0", forRemoval = true) -@SuppressWarnings("removal") -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 62f9b0df9998..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbCustomizer.java +++ /dev/null @@ -1,41 +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 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 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the - * new client and its own - * Spring Boot integration. - */ -@FunctionalInterface -@Deprecated(since = "3.2.0", forRemoval = true) -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 14995dba425f..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbOkHttpClientBuilderProvider.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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the - * new client and its own - * Spring Boot integration. - */ -@FunctionalInterface -@Deprecated(since = "3.2.0", forRemoval = true) -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 145c490c2762..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/influx/InfluxDbProperties.java +++ /dev/null @@ -1,81 +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 org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; - -/** - * Configuration properties for InfluxDB. - * - * @author Sergey Kuptsov - * @author Stephane Nicoll - * @since 2.0.0 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of the - * new InfluxDB Java - * client and its own Spring Boot integration. - */ -@Deprecated(since = "3.2.0", forRemoval = true) -@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; - - @DeprecatedConfigurationProperty(reason = "the new InfluxDb Java client provides Spring Boot integration", - since = "3.2.0") - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - @DeprecatedConfigurationProperty(reason = "the new InfluxDb Java client provides Spring Boot integration", - since = "3.2.0") - public String getUser() { - return this.user; - } - - public void setUser(String user) { - this.user = user; - } - - @DeprecatedConfigurationProperty(reason = "the new InfluxDb Java client provides Spring Boot integration", - since = "3.2.0") - 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 8498bf60e0eb..3a29c253fa23 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 @@ -24,7 +24,6 @@ import io.rsocket.transport.netty.server.TcpServerTransport; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; @@ -33,6 +32,7 @@ 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.autoconfigure.condition.ConditionalOnThreading; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; @@ -40,9 +40,11 @@ import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition; import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration; +import org.springframework.boot.autoconfigure.thread.Threading; 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.SimpleAsyncTaskSchedulerBuilder; import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -66,6 +68,7 @@ import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; @@ -80,6 +83,7 @@ * @author Stephane Nicoll * @author Vedran Pavic * @author Madhura Bhave + * @author Yong-Hyun Kim * @since 1.1.0 */ @AutoConfiguration(after = { DataSourceAutoConfiguration.class, JmxAutoConfiguration.class, @@ -166,25 +170,29 @@ private Trigger createPeriodicTrigger(Duration period, Duration initialDelay, bo } /** - * Expose a standard {@link ThreadPoolTaskScheduler} if the user has not enabled task - * scheduling explicitly. + * Expose a standard {@link org.springframework.scheduling.TaskScheduler + * TaskScheduler} if the user has not enabled task scheduling explicitly. A + * {@link SimpleAsyncTaskScheduler} is exposed if the user enables virtual threads via + * {@code spring.threads.virtual.enabled=true}, otherwise + * {@link ThreadPoolTaskScheduler}. */ @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(org.springframework.boot.task.TaskSchedulerBuilder.class) @ConditionalOnMissingBean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME) - @SuppressWarnings({ "deprecation", "removal" }) protected static class IntegrationTaskSchedulerConfiguration { @Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME) - public ThreadPoolTaskScheduler taskScheduler( - org.springframework.boot.task.TaskSchedulerBuilder taskSchedulerBuilder, - ObjectProvider threadPoolTaskSchedulerBuilderProvider) { - ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder = threadPoolTaskSchedulerBuilderProvider - .getIfUnique(); - if (threadPoolTaskSchedulerBuilder != null) { - return threadPoolTaskSchedulerBuilder.build(); - } - return taskSchedulerBuilder.build(); + @ConditionalOnBean(ThreadPoolTaskSchedulerBuilder.class) + @ConditionalOnThreading(Threading.PLATFORM) + public ThreadPoolTaskScheduler taskScheduler(ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder) { + return threadPoolTaskSchedulerBuilder.build(); + } + + @Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME) + @ConditionalOnBean(SimpleAsyncTaskSchedulerBuilder.class) + @ConditionalOnThreading(Threading.VIRTUAL) + public SimpleAsyncTaskScheduler taskSchedulerVirtualThreads( + SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilder) { + return simpleAsyncTaskSchedulerBuilder.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 d5bffd5f1bd9..5b7d2bbf764d 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 @@ -130,14 +130,14 @@ public static class Endpoint { private boolean throwExceptionOnLateReply = false; /** - * A comma-separated list of message header names that should not be populated - * into Message instances during a header copying operation. + * List of message header names that should not be populated into Message + * instances during a header copying operation. */ private List readOnlyHeaders = new ArrayList<>(); /** - * A comma-separated list of endpoint bean names patterns that should not be - * started automatically during application startup. + * List of endpoint bean names patterns that should not be started automatically + * during application startup. */ private List noAutoStartup = new ArrayList<>(); @@ -430,11 +430,10 @@ public static class Management { private boolean defaultLoggingEnabled = true; /** - * Comma-separated list of simple patterns to match against the names of Spring - * Integration components. When matched, observation instrumentation will be - * performed for the component. Please refer to the javadoc of the smartMatch - * method of Spring Integration's PatternMatchUtils for details of the pattern - * syntax. + * List of simple patterns to match against the names of Spring Integration + * components. When matched, observation instrumentation will be performed for the + * component. Please refer to the javadoc of the smartMatch method of Spring + * Integration's PatternMatchUtils for details of the pattern syntax. */ private List observationPatterns = new ArrayList<>(); 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 5ad8ff75ae83..0f85ec11034b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,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/DataSourceTransactionManagerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java index e2c5a63dae01..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 @@ -63,8 +63,7 @@ static class JdbcTransactionManagerConfiguration { DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource, ObjectProvider transactionManagerCustomizers) { DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource); - transactionManagerCustomizers - .ifAvailable((customizers) -> customizers.customize((TransactionManager) transactionManager)); + transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager)); return transactionManager; } 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 26e511dbb9f0..be9d767d0f26 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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 Stephane Nicoll * @author Eddú Meléndez * @author Vedran Pavic + * @author Lasse Wulff * @since 1.3.3 */ public final class DefaultJmsListenerContainerFactoryConfigurer { @@ -114,11 +115,13 @@ public void setObservationRegistry(ObservationRegistry observationRegistry) { public void configure(DefaultJmsListenerContainerFactory factory, ConnectionFactory connectionFactory) { Assert.notNull(factory, "Factory must not be null"); Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); - factory.setConnectionFactory(connectionFactory); - factory.setPubSubDomain(this.jmsProperties.isPubSubDomain()); JmsProperties.Listener listenerProperties = this.jmsProperties.getListener(); Session sessionProperties = listenerProperties.getSession(); + factory.setConnectionFactory(connectionFactory); 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); @@ -132,6 +135,7 @@ public void configure(DefaultJmsListenerContainerFactory factory, ConnectionFact map.from(listenerProperties::isAutoStartup).to(factory::setAutoStartup); map.from(listenerProperties::formatConcurrency).to(factory::setConcurrency); map.from(listenerProperties::getReceiveTimeout).as(Duration::toMillis).to(factory::setReceiveTimeout); + map.from(listenerProperties::getMaxMessagesPerTask).to(factory::setMaxMessagesPerTask); } } 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 508863683403..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. @@ -25,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; @@ -89,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/JmsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java index 08b7a6d5ee5e..aaf6c8a9ada6 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. @@ -27,6 +27,7 @@ * @author Greg Turnquist * @author Phillip Webb * @author Stephane Nicoll + * @author Lasse Wulff * @author Vedran Pavic * @since 1.0.0 */ @@ -44,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(); @@ -58,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; } @@ -159,6 +186,13 @@ public static class Listener { */ private Duration receiveTimeout = Duration.ofSeconds(1); + /** + * Specify the maximum number of messages to process in one task. By default, + * unlimited unless a SchedulingTaskExecutor is configured on the listener (10 + * messages), as it indicates a preference for short-lived tasks. + */ + private Integer maxMessagesPerTask; + private final Session session = new Session(); public boolean isAutoStartup() { @@ -223,6 +257,14 @@ public void setReceiveTimeout(Duration receiveTimeout) { this.receiveTimeout = receiveTimeout; } + public Integer getMaxMessagesPerTask() { + return this.maxMessagesPerTask; + } + + public void setMaxMessagesPerTask(Integer maxMessagesPerTask) { + this.maxMessagesPerTask = maxMessagesPerTask; + } + public Session getSession() { return this.session; } 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 2877479a08e6..dc4bb98a2b61 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ @ConfigurationProperties(prefix = "spring.activemq") public class ActiveMQProperties { + private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false"; + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; /** @@ -54,6 +56,8 @@ public class ActiveMQProperties { */ private String password; + private final Embedded embedded = new Embedded(); + /** * Time to wait before considering a close complete. */ @@ -99,6 +103,10 @@ public void setPassword(String password) { this.password = password; } + public Embedded getEmbedded() { + return this.embedded; + } + public Duration getCloseTimeout() { return this.closeTimeout; } @@ -135,9 +143,32 @@ String determineBrokerUrl() { if (this.brokerUrl != null) { return this.brokerUrl; } + if (this.embedded.isEnabled()) { + return DEFAULT_EMBEDDED_BROKER_URL; + } return DEFAULT_NETWORK_BROKER_URL; } + /** + * Configuration for an embedded ActiveMQ broker. + */ + public static class Embedded { + + /** + * Whether to enable embedded mode if the ActiveMQ Broker is available. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + public static class Packages { /** @@ -146,8 +177,7 @@ public static class Packages { private Boolean trustAll; /** - * Comma-separated list of specific packages to trust (when not trusting all - * packages). + * List of specific packages to trust (when not trusting all packages). */ private List trusted = new ArrayList<>(); 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 a6391df2bb9d..ab4e1f6939c4 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 @@ -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::new, ActiveMQConnectionFactory::new); } @@ -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::new, ActiveMQConnectionFactory::new); 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 5221b37be53e..957c17cc4b84 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 @@ -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(Function nativeFactoryCreator, @@ -82,7 +87,7 @@ private void startEmbeddedJms() { private T doCreateConnectionFactory(Function nativeFactoryCreator, Function embeddedFactoryCreator) throws Exception { - ArtemisMode mode = this.properties.getMode(); + ArtemisMode mode = this.connectionDetails.getMode(); if (mode == null) { mode = deduceMode(); } @@ -128,17 +133,17 @@ private T createEmbeddedConnectionFactory( private T createNativeConnectionFactory(Function factoryCreator) { T connectionFactory = newNativeConnectionFactory(factoryCreator); - 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(Function factoryCreator) { - 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; return factoryCreator.apply(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/ArtemisProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java index 15325c1c2768..5341b5ab710a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -131,12 +131,12 @@ public static class Embedded { private String dataDirectory; /** - * Comma-separated list of queues to create on startup. + * List of queues to create on startup. */ private String[] queues = new String[0]; /** - * Comma-separated list of topics to create on startup. + * List of topics to create on startup. */ private String[] topics = new String[0]; 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 28d8792d1368..0d71430d595a 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 @@ -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::new, ActiveMQXAConnectionFactory::new)); + ArtemisConnectionDetails connectionDetails, XAConnectionFactoryWrapper wrapper) throws Exception { + return wrapper + .wrapConnectionFactory(new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) + .createConnectionFactory(ActiveMQXAConnectionFactory::new, ActiveMQXAConnectionFactory::new)); } @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::new, ActiveMQXAConnectionFactory::new); } 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..ec63bae29713 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. @@ -37,7 +37,6 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; 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; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.transaction.PlatformTransactionManager; @@ -53,6 +52,7 @@ @AutoConfiguration(after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class }) @ConditionalOnClass(DSLContext.class) @ConditionalOnBean(DataSource.class) +@EnableConfigurationProperties(JooqProperties.class) public class JooqAutoConfiguration { @Bean @@ -70,35 +70,36 @@ public SpringTransactionProvider transactionProvider(PlatformTransactionManager @Bean @Order(0) - public DefaultExecuteListenerProvider jooqExceptionTranslatorExecuteListenerProvider() { - return new DefaultExecuteListenerProvider(new JooqExceptionTranslator()); + public DefaultExecuteListenerProvider jooqExceptionTranslatorExecuteListenerProvider( + ExceptionTranslatorExecuteListener exceptionTranslatorExecuteListener) { + return new DefaultExecuteListenerProvider(exceptionTranslatorExecuteListener); } - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(DSLContext.class) - @EnableConfigurationProperties(JooqProperties.class) - public static class DslContextConfiguration { - - @Bean - public DefaultDSLContext dslContext(org.jooq.Configuration configuration) { - return new DefaultDSLContext(configuration); - } + @Bean + @ConditionalOnMissingBean(ExceptionTranslatorExecuteListener.class) + public ExceptionTranslatorExecuteListener jooqExceptionTranslator() { + return ExceptionTranslatorExecuteListener.DEFAULT; + } - @Bean - @ConditionalOnMissingBean(org.jooq.Configuration.class) - public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider, - DataSource dataSource, ObjectProvider transactionProvider, - ObjectProvider executeListenerProviders, - ObjectProvider configurationCustomizers) { - DefaultConfiguration configuration = new DefaultConfiguration(); - configuration.set(properties.determineSqlDialect(dataSource)); - configuration.set(connectionProvider); - transactionProvider.ifAvailable(configuration::set); - configuration.set(executeListenerProviders.orderedStream().toArray(ExecuteListenerProvider[]::new)); - configurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration)); - return configuration; - } + @Bean + @ConditionalOnMissingBean(DSLContext.class) + public DefaultDSLContext dslContext(org.jooq.Configuration configuration) { + return new DefaultDSLContext(configuration); + } + @Bean + @ConditionalOnMissingBean(org.jooq.Configuration.class) + public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider, + DataSource dataSource, ObjectProvider transactionProvider, + ObjectProvider executeListenerProviders, + ObjectProvider configurationCustomizers) { + DefaultConfiguration configuration = new DefaultConfiguration(); + configuration.set(properties.determineSqlDialect(dataSource)); + configuration.set(connectionProvider); + transactionProvider.ifAvailable(configuration::set); + configuration.set(executeListenerProviders.orderedStream().toArray(ExecuteListenerProvider[]::new)); + configurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration)); + return configuration; } } 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/kafka/ConcurrentKafkaListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/ConcurrentKafkaListenerContainerFactoryConfigurer.java index 86cfd258ee93..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. @@ -237,7 +237,7 @@ private void configureContainer(ContainerProperties container) { map.from(properties::isMissingTopicsFatal).to(container::setMissingTopicsFatal); map.from(properties::isImmediateStop).to(container::setStopImmediate); map.from(properties::isObservationEnabled).to(container::setObservationEnabled); - map.from(this.transactionManager).to(container::setTransactionManager); + map.from(this.transactionManager).to(container::setKafkaAwareTransactionManager); map.from(this.rebalanceListener).to(container::setConsumerRebalanceListener); map.from(this.listenerTaskExecutor).to(container::setListenerTaskExecutor); } 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 18e02adb854a..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,7 +32,7 @@ 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; @@ -186,7 +186,7 @@ public RetryTopicConfiguration kafkaRetryTopicConfiguration(KafkaTemplate .useSingleTopicForSameIntervals() .suffixTopicsWithIndexValues() .doNotAutoCreateRetryTopics(); - setBackOffPolicy(builder, retryTopic); + setBackOffPolicy(builder, retryTopic.getBackoff()); return builder.create(kafkaTemplate); } @@ -214,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 726d3eedfb2e..e97fbde48753 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 @@ -65,8 +65,8 @@ public class KafkaProperties { /** - * Comma-delimited list of host:port pairs to use for establishing the initial - * connections to the Kafka cluster. Applies to all components unless overridden. + * List of host:port pairs to use for establishing the initial connections to the + * Kafka cluster. Applies to all components unless overridden. */ private List bootstrapServers = new ArrayList<>(Collections.singletonList("localhost:9092")); @@ -177,21 +177,6 @@ private Map buildCommonProperties(SslBundles sslBundles) { return properties; } - /** - * 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 {@code kafkaConsumerFactory} bean. - * @return the consumer properties initialized with the customizations defined on this - * instance - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of - * {@link #buildConsumerProperties(SslBundles)}} - */ - @Deprecated(since = "3.2.0", forRemoval = true) - public Map buildConsumerProperties() { - return buildConsumerProperties(null); - } - /** * Create an initial map of consumer properties from the state of this instance. *

@@ -207,21 +192,6 @@ public Map buildConsumerProperties(SslBundles sslBundles) { return properties; } - /** - * 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 {@code kafkaProducerFactory} bean. - * @return the producer properties initialized with the customizations defined on this - * instance - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of - * {@link #buildProducerProperties(SslBundles)}} - */ - @Deprecated(since = "3.2.0", forRemoval = true) - public Map buildProducerProperties() { - return buildProducerProperties(null); - } - /** * Create an initial map of producer properties from the state of this instance. *

@@ -237,21 +207,6 @@ public Map buildProducerProperties(SslBundles sslBundles) { return properties; } - /** - * 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 {@code kafkaAdmin} bean. - * @return the admin properties initialized with the customizations defined on this - * instance - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of - * {@link #buildAdminProperties(SslBundles)}} - */ - @Deprecated(since = "3.2.0", forRemoval = true) - public Map buildAdminProperties() { - return buildAdminProperties(null); - } - /** * Create an initial map of admin properties from the state of this instance. *

@@ -267,20 +222,6 @@ public Map buildAdminProperties(SslBundles sslBundles) { return properties; } - /** - * Create an initial map of streams properties from the state of this instance. - *

- * This allows you to add additional properties, if necessary. - * @return the streams properties initialized with the customizations defined on this - * instance - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of - * {@link #buildStreamsProperties(SslBundles)}} - */ - @Deprecated(since = "3.2.0", forRemoval = true) - public Map buildStreamsProperties() { - return buildStreamsProperties(null); - } - /** * Create an initial map of streams properties from the state of this instance. *

@@ -314,8 +255,8 @@ public static class Consumer { private String autoOffsetReset; /** - * Comma-delimited list of host:port pairs to use for establishing the initial - * connections to the Kafka cluster. Overrides the global property, for consumers. + * List of host:port pairs to use for establishing the initial connections to the + * Kafka cluster. Overrides the global property, for consumers. */ private List bootstrapServers; @@ -542,8 +483,8 @@ public static class Producer { private DataSize batchSize; /** - * Comma-delimited list of host:port pairs to use for establishing the initial - * connections to the Kafka cluster. Overrides the global property, for producers. + * List of host:port pairs to use for establishing the initial connections to the + * Kafka cluster. Overrides the global property, for producers. */ private List bootstrapServers; @@ -832,16 +773,11 @@ public static class Streams { private boolean autoStartup = true; /** - * Comma-delimited list of host:port pairs to use for establishing the initial - * connections to the Kafka cluster. Overrides the global property, for streams. + * List of host:port pairs to use for establishing the initial connections to the + * Kafka cluster. Overrides the global property, for 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. */ @@ -904,18 +840,6 @@ public void setBootstrapServers(List bootstrapServers) { this.bootstrapServers = bootstrapServers; } - @DeprecatedConfigurationProperty(replacement = "spring.kafka.streams.state-store-cache-max-size", - since = "3.1.0") - @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; } @@ -957,9 +881,6 @@ public Map buildProperties(SslBundles sslBundles) { 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")); @@ -1627,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; } @@ -1665,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; + } + } } 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 26c097a9be0c..469f6a93e94b 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 @@ -18,10 +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; @@ -49,6 +52,7 @@ import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** @@ -65,6 +69,7 @@ * @author Ferenc Gratzer * @author Evgeniy Cheban * @author Moritz Halbritter + * @author Ahmed Ashour * @since 1.1.0 */ @AutoConfiguration(after = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) @@ -94,14 +99,16 @@ PropertiesLiquibaseConnectionDetails liquibaseConnectionDetails(LiquibasePropert } @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()); liquibase.setClearCheckSums(properties.isClearChecksums()); - liquibase.setContexts(properties.getContexts()); + if (!CollectionUtils.isEmpty(properties.getContexts())) { + liquibase.setContexts(StringUtils.collectionToCommaDelimitedString(properties.getContexts())); + } liquibase.setDefaultSchema(properties.getDefaultSchema()); liquibase.setLiquibaseSchema(properties.getLiquibaseSchema()); liquibase.setLiquibaseTablespace(properties.getLiquibaseTablespace()); @@ -109,7 +116,9 @@ public SpringLiquibase liquibase(ObjectProvider dataSource, liquibase.setDatabaseChangeLogLockTable(properties.getDatabaseChangeLogLockTable()); liquibase.setDropFirst(properties.isDropFirst()); liquibase.setShouldRun(properties.isEnabled()); - liquibase.setLabelFilter(properties.getLabelFilter()); + if (!CollectionUtils.isEmpty(properties.getLabelFilter())) { + liquibase.setLabelFilter(StringUtils.collectionToCommaDelimitedString(properties.getLabelFilter())); + } liquibase.setChangeLogParameters(properties.getParameters()); liquibase.setRollbackFile(properties.getRollbackFile()); liquibase.setTestRollbackOnUpdate(properties.isTestRollbackOnUpdate()); @@ -121,6 +130,10 @@ public SpringLiquibase liquibase(ObjectProvider dataSource, 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; } @@ -169,6 +182,17 @@ private void applyConnectionDetails(LiquibaseConnectionDetails connectionDetails } + @ConditionalOnClass(Customizer.class) + static class CustomizerConfiguration { + + @Bean + @ConditionalOnBean(Customizer.class) + SpringLiquibaseCustomizer springLiquibaseCustomizer(Customizer customizer) { + return (springLiquibase) -> springLiquibase.setCustomizer(customizer); + } + + } + static final class LiquibaseDataSourceCondition extends AnyNestedCondition { LiquibaseDataSourceCondition() { @@ -235,4 +259,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 7b32afdadfde..7d1df833225a 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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,13 @@ package org.springframework.boot.autoconfigure.liquibase; import java.io.File; +import java.util.List; 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.util.Assert; @@ -50,9 +52,9 @@ public class LiquibaseProperties { private boolean clearChecksums; /** - * Comma-separated list of runtime contexts to use. + * List of runtime contexts to use. */ - private String contexts; + private List contexts; /** * Default database schema. @@ -111,9 +113,9 @@ public class LiquibaseProperties { private String url; /** - * Comma-separated list of runtime labels to use. + * List of runtime labels to use. */ - private String labelFilter; + private List labelFilter; /** * Change log parameters. @@ -147,6 +149,11 @@ public class LiquibaseProperties { */ private ShowSummaryOutput showSummaryOutput; + /** + * Which UIService to use. + */ + private UiService uiService; + public String getChangeLog() { return this.changeLog; } @@ -156,11 +163,11 @@ public void setChangeLog(String changeLog) { this.changeLog = changeLog; } - public String getContexts() { + public List getContexts() { return this.contexts; } - public void setContexts(String contexts) { + public void setContexts(List contexts) { this.contexts = contexts; } @@ -260,11 +267,11 @@ public void setUrl(String url) { this.url = url; } - public String getLabelFilter() { + public List getLabelFilter() { return this.labelFilter; } - public void setLabelFilter(String labelFilter) { + public void setLabelFilter(List labelFilter) { this.labelFilter = labelFilter; } @@ -316,6 +323,14 @@ 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 @@ -368,4 +383,23 @@ public enum ShowSummaryOutput { } + /** + * 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/mail/MailProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java index 5fecb40a6179..2b2e182998a2 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 @@ -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. @@ -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,43 @@ public String getJndiName() { return this.jndiName; } + public Ssl getSsl() { + return this.ssl; + } + + public static class Ssl { + + /** + * Whether to enable SSL support. If enabled, 'mail.(protocol).ssl.enable' + * property is set to 'true'. + */ + private boolean enabled = false; + + /** + * SSL bundle name. If set, 'mail.(protocol).ssl.socketFactory' property is set to + * an SSLSocketFactory obtained from the corresponding SSL bundle. + *

+ * Note that the STARTTLS command can use the corresponding SSLSocketFactory, even + * if the 'mail.(protocol).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..94cabfaa1cb1 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,20 @@ 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(); + protocol = (!StringUtils.hasLength(protocol)) ? "smtp" : protocol; + 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 f45770365aba..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizer.java +++ /dev/null @@ -1,118 +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.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; -import org.springframework.util.StringUtils; - -/** - * 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 (StringUtils.hasText(this.properties.getReplicaSetName())) { - 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 5426b6f17fc9..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. @@ -129,10 +129,8 @@ public void destroy() { } } - @SuppressWarnings("deprecation") private boolean isCustomTransportConfiguration(MongoClientSettings settings) { - return settings != null - && (settings.getTransportSettings() != null || settings.getStreamFactoryFactory() != null); + return settings != null && settings.getTransportSettings() != null; } } 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 90355acc6692..bf6a6ceff762 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. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,6 +47,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 +71,7 @@ * @author Andy Wilkinson * @author Kazuki Shimizu * @author Eddú Meléndez + * @author Yanming Zhou * @since 1.0.0 */ @Configuration(proxyBeanMethods = false) @@ -93,8 +96,7 @@ protected JpaBaseConfiguration(DataSource dataSource, JpaProperties properties, public PlatformTransactionManager transactionManager( ObjectProvider transactionManagerCustomizers) { JpaTransactionManager transactionManager = new JpaTransactionManager(); - transactionManagerCustomizers - .ifAvailable((customizers) -> customizers.customize((TransactionManager) transactionManager)); + transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager)); return transactionManager; } @@ -118,22 +120,27 @@ public JpaVendorAdapter jpaVendorAdapter() { public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, ObjectProvider persistenceUnitManager, ObjectProvider customizers) { - EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, - this.properties.getProperties(), persistenceUnitManager.getIfAvailable()); + EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, buildJpaProperties(), + persistenceUnitManager.getIfAvailable()); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder; } + private Map buildJpaProperties() { + Map properties = new HashMap<>(this.properties.getProperties()); + Map vendorProperties = getVendorProperties(); + customizeVendorProperties(vendorProperties); + properties.putAll(vendorProperties); + return properties; + } + @Bean @Primary @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class }) public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder, PersistenceManagedTypes persistenceManagedTypes) { - Map vendorProperties = getVendorProperties(); - customizeVendorProperties(vendorProperties); return factoryBuilder.dataSource(this.dataSource) .managedTypes(persistenceManagedTypes) - .properties(vendorProperties) .mappingResources(getMappingResources()) .jta(isJta()) .build(); @@ -195,9 +202,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/PulsarAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfiguration.java index 55fb759a86f6..a38a95f5a5db 100644 --- 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 @@ -52,11 +52,14 @@ import org.springframework.pulsar.core.PulsarProducerFactory; import org.springframework.pulsar.core.PulsarReaderFactory; import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.pulsar.core.PulsarTopicBuilder; 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. @@ -86,24 +89,31 @@ public class PulsarAutoConfiguration { @ConditionalOnMissingBean(PulsarProducerFactory.class) @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "false") DefaultPulsarProducerFactory pulsarProducerFactory(PulsarClient pulsarClient, TopicResolver topicResolver, - ObjectProvider> customizersProvider) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { List> lambdaSafeCustomizers = lambdaSafeProducerBuilderCustomizers( customizersProvider); - return new DefaultPulsarProducerFactory<>(pulsarClient, this.properties.getProducer().getTopicName(), - lambdaSafeCustomizers, topicResolver); + DefaultPulsarProducerFactory producerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, + this.properties.getProducer().getTopicName(), lambdaSafeCustomizers, topicResolver); + topicBuilderProvider.ifAvailable(producerFactory::setTopicBuilder); + return producerFactory; } @Bean @ConditionalOnMissingBean(PulsarProducerFactory.class) @ConditionalOnProperty(name = "spring.pulsar.producer.cache.enabled", havingValue = "true", matchIfMissing = true) CachingPulsarProducerFactory cachingPulsarProducerFactory(PulsarClient pulsarClient, TopicResolver topicResolver, - ObjectProvider> customizersProvider) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { 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()); + CachingPulsarProducerFactory producerFactory = new CachingPulsarProducerFactory<>(pulsarClient, + this.properties.getProducer().getTopicName(), lambdaSafeCustomizers, topicResolver, + cacheProperties.getExpireAfterAccess(), cacheProperties.getMaximumSize(), + cacheProperties.getInitialCapacity()); + topicBuilderProvider.ifAvailable(producerFactory::setTopicBuilder); + return producerFactory; } private List> lambdaSafeProducerBuilderCustomizers( @@ -126,20 +136,34 @@ private void applyProducerBuilderCustomizers(List> PulsarTemplate pulsarTemplate(PulsarProducerFactory pulsarProducerFactory, ObjectProvider producerInterceptors, SchemaResolver schemaResolver, TopicResolver topicResolver) { - return new PulsarTemplate<>(pulsarProducerFactory, producerInterceptors.orderedStream().toList(), - schemaResolver, topicResolver, this.properties.getTemplate().isObservationsEnabled()); + 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) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { 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); + DefaultPulsarConsumerFactory consumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, + lambdaSafeCustomizers); + topicBuilderProvider.ifAvailable(consumerFactory::setTopicBuilder); + return consumerFactory; + } + + @Bean + @ConditionalOnMissingBean(PulsarAwareTransactionManager.class) + @ConditionalOnProperty(prefix = "spring.pulsar.transaction", name = "enabled") + public PulsarTransactionManager pulsarTransactionManager(PulsarClient pulsarClient) { + return new PulsarTransactionManager(pulsarClient); } @SuppressWarnings("unchecked") @@ -153,27 +177,36 @@ private void applyConsumerBuilderCustomizers(List> @ConditionalOnMissingBean(name = "pulsarListenerContainerFactory") ConcurrentPulsarListenerContainerFactory pulsarListenerContainerFactory( PulsarConsumerFactory pulsarConsumerFactory, SchemaResolver schemaResolver, - TopicResolver topicResolver, Environment environment) { + TopicResolver topicResolver, ObjectProvider pulsarTransactionManager, + Environment environment, PulsarContainerFactoryCustomizers containerFactoryCustomizers) { 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); + ConcurrentPulsarListenerContainerFactory containerFactory = new ConcurrentPulsarListenerContainerFactory<>( + pulsarConsumerFactory, containerProperties); + containerFactoryCustomizers.customize(containerFactory); + return containerFactory; } @Bean @ConditionalOnMissingBean(PulsarReaderFactory.class) DefaultPulsarReaderFactory pulsarReaderFactory(PulsarClient pulsarClient, - ObjectProvider> customizersProvider) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { 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); + DefaultPulsarReaderFactory readerFactory = new DefaultPulsarReaderFactory<>(pulsarClient, + lambdaSafeCustomizers); + topicBuilderProvider.ifAvailable(readerFactory::setTopicBuilder); + return readerFactory; } @SuppressWarnings("unchecked") @@ -185,14 +218,18 @@ private void applyReaderBuilderCustomizers(List> cust @Bean @ConditionalOnMissingBean(name = "pulsarReaderContainerFactory") DefaultPulsarReaderContainerFactory pulsarReaderContainerFactory(PulsarReaderFactory pulsarReaderFactory, - SchemaResolver schemaResolver, Environment environment) { + SchemaResolver schemaResolver, Environment environment, + PulsarContainerFactoryCustomizers containerFactoryCustomizers) { 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); + DefaultPulsarReaderContainerFactory containerFactory = new DefaultPulsarReaderContainerFactory<>( + pulsarReaderFactory, readerContainerProperties); + containerFactoryCustomizers.customize(containerFactory); + return containerFactory; } @Configuration(proxyBeanMethods = false) 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 index 64efd410ccfa..6716f86eb016 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +22,8 @@ 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.naming.TopicDomain; import org.apache.pulsar.common.schema.SchemaType; import org.springframework.beans.factory.ObjectProvider; @@ -35,6 +35,7 @@ import org.springframework.boot.util.LambdaSafe; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; import org.springframework.pulsar.core.DefaultPulsarClientFactory; import org.springframework.pulsar.core.DefaultSchemaResolver; import org.springframework.pulsar.core.DefaultTopicResolver; @@ -42,6 +43,7 @@ import org.springframework.pulsar.core.PulsarAdministration; import org.springframework.pulsar.core.PulsarClientBuilderCustomizer; import org.springframework.pulsar.core.PulsarClientFactory; +import org.springframework.pulsar.core.PulsarTopicBuilder; import org.springframework.pulsar.core.SchemaResolver; import org.springframework.pulsar.core.SchemaResolver.SchemaResolverCustomizer; import org.springframework.pulsar.core.TopicResolver; @@ -96,7 +98,7 @@ private void applyClientBuilderCustomizers(List c @Bean @ConditionalOnMissingBean - PulsarClient pulsarClient(PulsarClientFactory clientFactory) throws PulsarClientException { + PulsarClient pulsarClient(PulsarClientFactory clientFactory) { return clientFactory.createClient(); } @@ -177,4 +179,20 @@ PulsarFunctionAdministration pulsarFunctionAdministration(PulsarAdministration p properties.isFailFast(), properties.isPropagateFailures(), properties.isPropagateStopFailures()); } + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "spring.pulsar.defaults.topic.enabled", havingValue = "true", matchIfMissing = true) + PulsarTopicBuilder pulsarTopicBuilder() { + return new PulsarTopicBuilder(TopicDomain.persistent, this.properties.getDefaults().getTopic().getTenant(), + this.properties.getDefaults().getTopic().getNamespace()); + } + + @Bean + @ConditionalOnMissingBean + PulsarContainerFactoryCustomizers pulsarContainerFactoryCustomizers( + ObjectProvider> customizers) { + return new PulsarContainerFactoryCustomizers(customizers.orderedStream().toList()); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizer.java new file mode 100644 index 000000000000..17e3de79b45d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizer.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.autoconfigure.pulsar; + +import org.springframework.pulsar.config.PulsarContainerFactory; + +/** + * Callback interface that can be implemented by beans wishing to customize a + * {@link PulsarContainerFactory} before it is fully initialized, in particular to tune + * its configuration. + * + * @param the type of the {@link PulsarContainerFactory} + * @author Chris Bono + * @since 3.4.0 + */ +@FunctionalInterface +public interface PulsarContainerFactoryCustomizer> { + + /** + * Customize the container factory. + * @param containerFactory the {@code PulsarContainerFactory} to customize + */ + void customize(T containerFactory); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizers.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizers.java new file mode 100644 index 000000000000..4109086a9760 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizers.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.pulsar; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.util.LambdaSafe; +import org.springframework.pulsar.config.PulsarContainerFactory; +import org.springframework.pulsar.core.PulsarConsumerFactory; + +/** + * Invokes the available {@link PulsarContainerFactoryCustomizer} instances in the context + * for a given {@link PulsarConsumerFactory}. + * + * @author Chris Bono + */ +class PulsarContainerFactoryCustomizers { + + private final List> customizers; + + PulsarContainerFactoryCustomizers(List> customizers) { + this.customizers = (customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList(); + } + + /** + * Customize the specified {@link PulsarContainerFactory}. Locates all + * {@link PulsarContainerFactoryCustomizer} beans able to handle the specified + * instance and invoke {@link PulsarContainerFactoryCustomizer#customize} on them. + * @param the type of container factory + * @param containerFactory the container factory to customize + * @return the customized container factory + */ + @SuppressWarnings("unchecked") + > T customize(T containerFactory) { + LambdaSafe.callbacks(PulsarContainerFactoryCustomizer.class, this.customizers, containerFactory) + .withLogger(PulsarContainerFactoryCustomizers.class) + .invoke((customizer) -> customizer.customize(containerFactory)); + return containerFactory; + } + +} 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 index 7597da0c08d1..3972eec5029f 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.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; @@ -42,6 +43,8 @@ * * @author Chris Bono * @author Phillip Webb + * @author Swamy Mavuri + * @author Vedran Pavic * @since 3.2.0 */ @ConfigurationProperties("spring.pulsar") @@ -65,6 +68,8 @@ public class PulsarProperties { private final Template template = new Template(); + private final Transaction transaction = new Transaction(); + public Client getClient() { return this.client; } @@ -101,6 +106,10 @@ public Template getTemplate() { return this.template; } + public Transaction getTransaction() { + return this.transaction; + } + public static class Client { /** @@ -128,6 +137,16 @@ public static class Client { */ private final Authentication authentication = new Authentication(); + /** + * Thread related configuration. + */ + private final Threads threads = new Threads(); + + /** + * Failover settings. + */ + private final Failover failover = new Failover(); + public String getServiceUrl() { return this.serviceUrl; } @@ -164,6 +183,14 @@ public Authentication getAuthentication() { return this.authentication; } + public Threads getThreads() { + return this.threads; + } + + public Failover getFailover() { + return this.failover; + } + } public static class Admin { @@ -240,6 +267,8 @@ public static class Defaults { */ private List typeMappings = new ArrayList<>(); + private final Topic topic = new Topic(); + public List getTypeMappings() { return this.typeMappings; } @@ -248,6 +277,10 @@ public void setTypeMappings(List typeMappings) { this.typeMappings = typeMappings; } + public Topic getTopic() { + return this.topic; + } + /** * 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. @@ -284,6 +317,38 @@ public record SchemaInfo(SchemaType schemaType, Class messageKeyType) { } + public static class Topic { + + /** + * Default tenant to use when producing or consuming messages against a + * non-fully-qualified topic URL. + */ + private String tenant = "public"; + + /** + * Default namespace to use when producing or consuming messages against a + * non-fully-qualified topic URL. + */ + private String namespace = "default"; + + public String getTenant() { + return this.tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getNamespace() { + return this.namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + } + } public static class Function { @@ -746,11 +811,16 @@ public static class Listener { */ private SchemaType schemaType; + /** + * Number of threads used by listener container. + */ + private Integer concurrency; + /** * Whether to record observations for when the Observations API is available and * the client supports it. */ - private boolean observationEnabled = true; + private boolean observationEnabled; public SchemaType getSchemaType() { return this.schemaType; @@ -760,6 +830,14 @@ public void setSchemaType(SchemaType schemaType) { this.schemaType = schemaType; } + public Integer getConcurrency() { + return this.concurrency; + } + + public void setConcurrency(Integer concurrency) { + this.concurrency = concurrency; + } + public boolean isObservationEnabled() { return this.observationEnabled; } @@ -778,7 +856,7 @@ public static class Reader { private String name; /** - * Topis the reader subscribes to. + * Topics the reader subscribes to. */ private List topics; @@ -845,7 +923,7 @@ public static class Template { /** * Whether to record observations for when the Observations API is available. */ - private boolean observationsEnabled = true; + private boolean observationsEnabled; public boolean isObservationsEnabled() { return this.observationsEnabled; @@ -857,6 +935,23 @@ public void setObservationsEnabled(boolean 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 { /** @@ -887,4 +982,133 @@ public void setParam(Map param) { } + public static class Threads { + + /** + * Number of threads to be used for handling connections to brokers. + */ + private Integer io; + + /** + * Number of threads to be used for message listeners. + */ + private Integer listener; + + public Integer getIo() { + return this.io; + } + + public void setIo(Integer io) { + this.io = io; + } + + public Integer getListener() { + return this.listener; + } + + public void setListener(Integer listener) { + this.listener = listener; + } + + } + + 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 index 99b1b8cd21a0..cfe34eb6f8ba 100644 --- 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 @@ -18,21 +18,28 @@ 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 java.util.stream.Collectors; 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; @@ -42,9 +49,14 @@ * * @author Chris Bono * @author Phillip Webb + * @author Swamy Mavuri + * @author Vedran Pavic */ final class PulsarPropertiesMapper { + private static final JsonWriter> jsonWriter = JsonWriter + .of((members) -> members.add().as(TreeMap::new).usingPairs(Map::forEach)); + private final PulsarProperties properties; PulsarPropertiesMapper(PulsarProperties properties) { @@ -54,11 +66,54 @@ final class PulsarPropertiesMapper { void customizeClientBuilder(ClientBuilder clientBuilder, PulsarConnectionDetails connectionDetails) { PulsarProperties.Client properties = this.properties.getClient(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(connectionDetails::getBrokerUrl).to(clientBuilder::serviceUrl); map.from(properties::getConnectionTimeout).to(timeoutProperty(clientBuilder::connectionTimeout)); map.from(properties::getOperationTimeout).to(timeoutProperty(clientBuilder::operationTimeout)); map.from(properties::getLookupTimeout).to(timeoutProperty(clientBuilder::lookupTimeout)); - customizeAuthentication(clientBuilder::authentication, properties.getAuthentication()); + map.from(properties.getThreads()::getIo).to(clientBuilder::ioThreads); + map.from(properties.getThreads()::getListener).to(clientBuilder::listenerThreads); + 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) { @@ -68,15 +123,14 @@ void customizeAdminBuilder(PulsarAdminBuilder adminBuilder, PulsarConnectionDeta 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(adminBuilder::authentication, properties.getAuthentication()); + customizeAuthentication(properties.getAuthentication(), adminBuilder::authentication); } - private void customizeAuthentication(AuthenticationConsumer authentication, - PulsarProperties.Authentication properties) { - if (StringUtils.hasText(properties.getPluginClassName())) { + private void customizeAuthentication(PulsarProperties.Authentication properties, AuthenticationConsumer action) { + String pluginClassName = properties.getPluginClassName(); + if (StringUtils.hasText(pluginClassName)) { try { - authentication.accept(properties.getPluginClassName(), - getAuthenticationParamsJson(properties.getParam())); + action.accept(pluginClassName, jsonWriter.writeToString(properties.getParam())); } catch (UnsupportedAuthenticationException ex) { throw new IllegalStateException("Unable to configure Pulsar authentication", ex); @@ -84,30 +138,6 @@ private void customizeAuthentication(AuthenticationConsumer authentication, } } - private String getAuthenticationParamsJson(Map params) { - Map sortedParams = new TreeMap<>(params); - try { - return sortedParams.entrySet() - .stream() - .map((entry) -> "\"%s\":\"%s\"".formatted(entry.getKey(), escapeJson(entry.getValue()))) - .collect(Collectors.joining(",", "{", "}")); - } - catch (Exception ex) { - throw new IllegalStateException("Could not convert auth parameters to encoded string", ex); - } - } - - private String escapeJson(String raw) { - return raw.replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("/", "\\/") - .replace("\b", "\\b") - .replace("\t", "\\t") - .replace("\n", "\\n") - .replace("\f", "\\f") - .replace("\r", "\\r"); - } - void customizeProducerBuilder(ProducerBuilder producerBuilder) { PulsarProperties.Producer properties = this.properties.getProducer(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); @@ -122,6 +152,10 @@ void customizeProducerBuilder(ProducerBuilder producerBuilder) { 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(); @@ -148,18 +182,21 @@ private void customizeConsumerBuilderSubscription(ConsumerBuilder consumerBui 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); + map.from(properties::getName).to(containerProperties::setSubscriptionName); } 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::getConcurrency).to(containerProperties::setConcurrency); map.from(properties::isObservationEnabled).to(containerProperties::setObservationEnabled); } 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 index 4c2aeb172d52..45d8d3037212 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.pulsar.config.PulsarAnnotationSupportBeanNames; +import org.springframework.pulsar.core.PulsarTopicBuilder; import org.springframework.pulsar.core.SchemaResolver; import org.springframework.pulsar.core.TopicResolver; import org.springframework.pulsar.reactive.config.DefaultReactivePulsarListenerContainerFactory; @@ -48,6 +49,7 @@ 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.DefaultReactivePulsarSenderFactory.Builder; import org.springframework.pulsar.reactive.core.ReactiveMessageConsumerBuilderCustomizer; import org.springframework.pulsar.reactive.core.ReactiveMessageReaderBuilderCustomizer; import org.springframework.pulsar.reactive.core.ReactiveMessageSenderBuilderCustomizer; @@ -112,17 +114,19 @@ private ReactiveMessageSenderCache reactivePulsarMessageSenderCache(ProducerCach @ConditionalOnMissingBean(ReactivePulsarSenderFactory.class) DefaultReactivePulsarSenderFactory reactivePulsarSenderFactory(ReactivePulsarClient reactivePulsarClient, ObjectProvider reactiveMessageSenderCache, TopicResolver topicResolver, - ObjectProvider> customizersProvider) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { 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) + Builder senderFactoryBuilder = DefaultReactivePulsarSenderFactory.builderFor(reactivePulsarClient) .withDefaultConfigCustomizers(lambdaSafeCustomizers) .withMessageSenderCache(reactiveMessageSenderCache.getIfAvailable()) - .withTopicResolver(topicResolver) - .build(); + .withTopicResolver(topicResolver); + topicBuilderProvider.ifAvailable(senderFactoryBuilder::withTopicBuilder); + return senderFactoryBuilder.build(); } @SuppressWarnings("unchecked") @@ -136,13 +140,17 @@ private void applyMessageSenderBuilderCustomizers(List reactivePulsarConsumerFactory( ReactivePulsarClient pulsarReactivePulsarClient, - ObjectProvider> customizersProvider) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { 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); + DefaultReactivePulsarConsumerFactory consumerFactory = new DefaultReactivePulsarConsumerFactory<>( + pulsarReactivePulsarClient, lambdaSafeCustomizers); + topicBuilderProvider.ifAvailable(consumerFactory::setTopicBuilder); + return consumerFactory; } @SuppressWarnings("unchecked") @@ -156,24 +164,31 @@ private void applyMessageConsumerBuilderCustomizers(List reactivePulsarListenerContainerFactory( ReactivePulsarConsumerFactory reactivePulsarConsumerFactory, SchemaResolver schemaResolver, - TopicResolver topicResolver) { + TopicResolver topicResolver, PulsarContainerFactoryCustomizers containerFactoryCustomizers) { ReactivePulsarContainerProperties containerProperties = new ReactivePulsarContainerProperties<>(); containerProperties.setSchemaResolver(schemaResolver); containerProperties.setTopicResolver(topicResolver); this.propertiesMapper.customizeContainerProperties(containerProperties); - return new DefaultReactivePulsarListenerContainerFactory<>(reactivePulsarConsumerFactory, containerProperties); + DefaultReactivePulsarListenerContainerFactory containerFactory = new DefaultReactivePulsarListenerContainerFactory<>( + reactivePulsarConsumerFactory, containerProperties); + containerFactoryCustomizers.customize(containerFactory); + return containerFactory; } @Bean @ConditionalOnMissingBean(ReactivePulsarReaderFactory.class) DefaultReactivePulsarReaderFactory reactivePulsarReaderFactory(ReactivePulsarClient reactivePulsarClient, - ObjectProvider> customizersProvider) { + ObjectProvider> customizersProvider, + ObjectProvider topicBuilderProvider) { 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); + DefaultReactivePulsarReaderFactory readerFactory = new DefaultReactivePulsarReaderFactory<>( + reactivePulsarClient, lambdaSafeCustomizers); + topicBuilderProvider.ifAvailable(readerFactory::setTopicBuilder); + return readerFactory; } @SuppressWarnings("unchecked") 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 index 2f79bbae615f..f936a6c8afcd 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 Chris Bono * @author Phillip Webb + * @author Vedran Pavic */ final class PulsarReactivePropertiesMapper { @@ -87,12 +88,14 @@ private void customizePulsarContainerConsumerSubscriptionProperties( PulsarProperties.Consumer.Subscription properties = this.properties.getConsumer().getSubscription(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(properties::getType).to(containerProperties::setSubscriptionType); + map.from(properties::getName).to(containerProperties::setSubscriptionName); } private void customizePulsarContainerListenerProperties(ReactivePulsarContainerProperties containerProperties) { PulsarProperties.Listener properties = this.properties.getListener(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(properties::getSchemaType).to(containerProperties::setSchemaType); + map.from(properties::getConcurrency).to(containerProperties::setConcurrency); } void customizeMessageReaderBuilder(ReactiveMessageReaderBuilder builder) { 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..013367d863de --- /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.Builder; + +/** + * Callback interface that can be used to customize a {@link Builder}. + * + * @author Tadaya Tsuyukubo + * @since 3.4.0 + */ +@FunctionalInterface +public interface ProxyConnectionFactoryCustomizer { + + /** + * Callback to customize a {@link Builder} instance. + * @param builder the builder to customize + */ + void customize(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/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 5f5cba160eaa..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. @@ -26,12 +26,14 @@ import java.util.Set; 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; @@ -48,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 +67,7 @@ * @author Anastasiia Losieva * @author Mushtaq Ahmed * @author Roman Golovin + * @author Yan Kardziyaka */ @Configuration(proxyBeanMethods = false) class ReactiveOAuth2ResourceServerJwkConfiguration { @@ -161,6 +167,35 @@ SupplierReactiveJwtDecoder jwtDecoderByIssuerUri( } + @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 { @@ -179,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 84bafab99db0..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. @@ -26,6 +26,7 @@ import java.util.Set; 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; @@ -33,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; @@ -48,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 +67,7 @@ * @author HaiTao Zhang * @author Mushtaq Ahmed * @author Roman Golovin + * @author Yan Kardziyaka */ @Configuration(proxyBeanMethods = false) class OAuth2ResourceServerJwtConfiguration { @@ -173,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/OAuth2AuthorizationServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfiguration.java index 1f011a9d8e76..979fa71f4e8d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ *

* Note: This configuration and * {@link OAuth2AuthorizationServerJwtAutoConfiguration} work together to ensure that the - * {@link org.springframework.security.config.annotation.ObjectPostProcessor} is defined + * {@link org.springframework.security.config.ObjectPostProcessor} is defined * BEFORE {@link UserDetailsServiceAutoConfiguration} so that a * {@link org.springframework.security.core.userdetails.UserDetailsService} can be created * if necessary. 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 bf85bed26f73..72bec9c23ecb 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 @@ -53,6 +53,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/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java index 5c36f2b4622e..5fe726266699 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,6 @@ import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; @@ -51,7 +50,11 @@ class OAuth2AuthorizationServerWebSecurityConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + OAuth2AuthorizationServerConfigurer authorizationServer = OAuth2AuthorizationServerConfigurer + .authorizationServer(); + http.securityMatcher(authorizationServer.getEndpointsMatcher()); + http.with(authorizationServer, withDefaults()); + http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(withDefaults()); http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults())); http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor( 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..aabed0af1bc6 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. @@ -17,7 +17,7 @@ package org.springframework.boot.autoconfigure.security.saml2; import java.io.InputStream; -import java.security.cert.CertificateFactory; +import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.util.Collection; @@ -32,16 +32,16 @@ import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration.Signing; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.ssl.pem.PemContent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; -import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; @@ -56,6 +56,8 @@ * @author Phillip Webb * @author Moritz Halbritter * @author Lasse Lindqvist + * @author Lasse Wulff + * @author Scott Frederick */ @Configuration(proxyBeanMethods = false) @Conditional(RegistrationConfiguredCondition.class) @@ -82,7 +84,7 @@ private RelyingPartyRegistration asRegistration(String id, Registration properti : createBuilderUsingMetadata(properties.getAssertingparty()).registrationId(id); builder.assertionConsumerServiceLocation(properties.getAcs().getLocation()); builder.assertionConsumerServiceBinding(properties.getAcs().getBinding()); - builder.assertingPartyDetails(mapAssertingParty(properties.getAssertingparty())); + builder.assertingPartyMetadata(mapAssertingParty(properties.getAssertingparty())); builder.signingX509Credentials((credentials) -> properties.getSigning() .getCredentials() .stream() @@ -93,7 +95,7 @@ private RelyingPartyRegistration asRegistration(String id, Registration properti .stream() .map(this::asDecryptionCredential) .forEach(credentials::add)); - builder.assertingPartyDetails( + builder.assertingPartyMetadata( (details) -> details.verificationX509Credentials((credentials) -> properties.getAssertingparty() .getVerification() .getCredentials() @@ -104,8 +106,9 @@ 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(); + boolean signRequest = registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned(); validateSigningCredentials(properties, signRequest); return registration; } @@ -124,11 +127,11 @@ private RelyingPartyRegistration.Builder createBuilderUsingMetadata(AssertingPar private Object getEntityId(RelyingPartyRegistration.Builder candidate) { String[] result = new String[1]; - candidate.assertingPartyDetails((builder) -> result[0] = builder.build().getEntityId()); + candidate.assertingPartyMetadata((builder) -> result[0] = builder.build().getEntityId()); return result[0]; } - private Consumer mapAssertingParty(AssertingParty assertingParty) { + private Consumer> mapAssertingParty(AssertingParty assertingParty) { return (details) -> { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(assertingParty::getEntityId).to(details::entityId); @@ -170,7 +173,11 @@ private RSAPrivateKey readPrivateKey(Resource location) { Assert.state(location != null, "No private key location specified"); Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist"); try (InputStream inputStream = location.getInputStream()) { - return RsaKeyConverters.pkcs8().convert(inputStream); + PemContent pemContent = PemContent.load(inputStream); + PrivateKey privateKey = pemContent.getPrivateKey(); + Assert.isInstanceOf(RSAPrivateKey.class, privateKey, + "PrivateKey in resource '" + location + "' must be an RSAPrivateKey"); + return (RSAPrivateKey) privateKey; } catch (Exception ex) { throw new IllegalArgumentException(ex); @@ -181,7 +188,9 @@ private X509Certificate readCertificate(Resource location) { Assert.state(location != null, "No certificate location specified"); Assert.state(location.exists(), () -> "Certificate location '" + location + "' does not exist"); try (InputStream inputStream = location.getInputStream()) { - return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream); + PemContent pemContent = PemContent.load(inputStream); + List certificates = pemContent.getCertificates(); + return certificates.get(0); } catch (Exception ex) { throw new IllegalArgumentException(ex); 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 9c4fef69ea0a..eac402fe0afc 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 @@ -38,7 +38,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java index 94adbae6f1df..b380d495088c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 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.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; @@ -28,7 +29,11 @@ import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; +import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository; import org.springframework.session.data.redis.ReactiveRedisSessionRepository; +import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction; +import org.springframework.session.data.redis.config.annotation.ConfigureNotifyKeyspaceEventsReactiveAction; +import org.springframework.session.data.redis.config.annotation.web.server.RedisIndexedWebSessionConfiguration; import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration; /** @@ -43,20 +48,58 @@ @ConditionalOnMissingBean(ReactiveSessionRepository.class) @ConditionalOnBean(ReactiveRedisConnectionFactory.class) @EnableConfigurationProperties(RedisSessionProperties.class) -@Import(RedisWebSessionConfiguration.class) class RedisReactiveSessionConfiguration { - @Bean - ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, - ServerProperties serverProperties) { - return (sessionRepository) -> { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sessionProperties.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); - map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); - }; + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "spring.session.redis", name = "repository-type", havingValue = "default", + matchIfMissing = true) + @Import(RedisWebSessionConfiguration.class) + static class DefaultRedisSessionConfiguration { + + @Bean + ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, + ServerProperties serverProperties) { + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); + map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); + }; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "spring.session.redis", name = "repository-type", havingValue = "indexed") + @Import(RedisIndexedWebSessionConfiguration.class) + static class IndexedRedisSessionConfiguration { + + @Bean + @ConditionalOnMissingBean + ConfigureReactiveRedisAction configureReactiveRedisAction(RedisSessionProperties redisSessionProperties) { + return switch (redisSessionProperties.getConfigureAction()) { + case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsReactiveAction(); + case NONE -> ConfigureReactiveRedisAction.NO_OP; + }; + } + + @Bean + ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, + ServerProperties serverProperties) { + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); + map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); + }; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java index 8b2042567ae4..93f16dc5ca1f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.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,7 +36,7 @@ public class RedisSessionProperties { /** * Sessions flush mode. Determines when session changes are written to the session - * store. + * store. Not supported with a reactive session repository. */ private FlushMode flushMode = FlushMode.ON_SAVE; @@ -47,14 +47,15 @@ public class RedisSessionProperties { private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE; /** - * The configure action to apply when no user defined ConfigureRedisAction bean is - * present. + * The configure action to apply when no user-defined ConfigureRedisAction or + * ConfigureReactiveRedisAction bean is present. */ private ConfigureAction configureAction = ConfigureAction.NOTIFY_KEYSPACE_EVENTS; /** * Cron expression for expired session cleanup job. Only supported when - * repository-type is set to indexed. + * repository-type is set to indexed. Not supported with a reactive session + * repository. */ private String cleanupCron; @@ -135,12 +136,13 @@ public enum ConfigureAction { public enum RepositoryType { /** - * Auto-configure a RedisSessionRepository. + * Auto-configure a RedisSessionRepository or ReactiveRedisSessionRepository. */ DEFAULT, /** - * Auto-configure a RedisIndexedSessionRepository. + * Auto-configure a RedisIndexedSessionRepository or + * ReactiveRedisIndexedSessionRepository. */ INDEXED 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 b5e36068a8a3..c5aadb811073 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,6 +98,7 @@ DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties, map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie); map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(cookieSerializer::setCookieMaxAge); map.from(cookie::getSameSite).as(SameSite::attributeValue).to(cookieSerializer::setSameSite); + map.from(cookie::getPartitioned).to(cookieSerializer::setPartitioned); cookieSerializerCustomizers.orderedStream().forEach((customizer) -> customizer.customize(cookieSerializer)); return cookieSerializer; } 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 index 96fe6f621f7a..fdb251c4576f 100644 --- 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 @@ -16,13 +16,12 @@ package org.springframework.boot.autoconfigure.ssl; -import java.io.FileNotFoundException; -import java.net.URL; import java.nio.file.Path; import org.springframework.boot.ssl.pem.PemContent; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; -import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -52,13 +51,14 @@ boolean hasValue() { return StringUtils.hasText(this.value); } - Path toWatchPath() { + Path toWatchPath(ResourceLoader resourceLoader) { try { - URL url = toUrl(); - if (!isFileUrl(url)) { + Assert.state(!isPemContent(), "Value contains PEM content"); + Resource resource = resourceLoader.getResource(this.value); + if (!resource.isFile()) { throw new BundleContentNotWatchableException(this); } - return Path.of(url.toURI()).toAbsolutePath(); + return Path.of(resource.getFile().getAbsolutePath()); } catch (Exception ex) { if (ex instanceof BundleContentNotWatchableException bundleContentNotWatchableException) { @@ -69,13 +69,4 @@ Path toWatchPath() { } } - private URL toUrl() throws FileNotFoundException { - Assert.state(!isPemContent(), "Value contains PEM content"); - return ResourceUtils.getURL(this.value); - } - - private boolean isFileUrl(URL url) { - return "file".equalsIgnoreCase(url.getProtocol()); - } - } 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 29baa16e69ee..1a8a35aa4e0a 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 @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.ssl; import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key; +import org.springframework.boot.io.ApplicationResourceLoader; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundleKey; import org.springframework.boot.ssl.SslManagerBundle; @@ -27,6 +28,7 @@ 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.io.ResourceLoader; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; @@ -97,18 +99,31 @@ public SslManagerBundle getManagers() { * @return an {@link SslBundle} instance */ public static SslBundle get(PemSslBundleProperties properties) { - PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore()); + return get(properties, ApplicationResourceLoader.get()); + } + + /** + * Get an {@link SslBundle} for the given {@link PemSslBundleProperties}. + * @param properties the source properties + * @param resourceLoader the resource loader used to load content + * @return an {@link SslBundle} instance + * @since 3.3.5 + */ + public static SslBundle get(PemSslBundleProperties properties, ResourceLoader resourceLoader) { + PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore(), resourceLoader); if (keyStore != null) { keyStore = keyStore.withAlias(properties.getKey().getAlias()) .withPassword(properties.getKey().getPassword()); } - PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore()); + PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore(), resourceLoader); 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)); + private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties, + ResourceLoader resourceLoader) { + PemSslStoreDetails details = asPemSslStoreDetails(properties); + PemSslStore pemSslStore = PemSslStore.load(details, resourceLoader); if (properties.isVerifyKeys()) { CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey()); Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()), @@ -128,14 +143,25 @@ private static PemSslStoreDetails asPemSslStoreDetails(PemSslBundleProperties.St * @return an {@link SslBundle} instance */ public static SslBundle get(JksSslBundleProperties properties) { - SslStoreBundle storeBundle = asSslStoreBundle(properties); + return get(properties, ApplicationResourceLoader.get()); + } + + /** + * Get an {@link SslBundle} for the given {@link JksSslBundleProperties}. + * @param properties the source properties + * @param resourceLoader the resource loader used to load content + * @return an {@link SslBundle} instance + * @since 3.3.5 + */ + public static SslBundle get(JksSslBundleProperties properties, ResourceLoader resourceLoader) { + SslStoreBundle storeBundle = asSslStoreBundle(properties, resourceLoader); return new PropertiesSslBundle(storeBundle, properties); } - private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) { + private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties, ResourceLoader resourceLoader) { JksSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore()); JksSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore()); - return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails); + return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails, resourceLoader); } private static JksSslStoreDetails asStoreDetails(JksSslBundleProperties.Store 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 1348f16b37b8..46af4615a0e8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.io.ApplicationResourceLoader; import org.springframework.boot.ssl.DefaultSslBundleRegistry; import org.springframework.boot.ssl.SslBundleRegistry; import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ResourceLoader; /** * {@link EnableAutoConfiguration Auto-configuration} for SSL. @@ -36,9 +38,12 @@ @EnableConfigurationProperties(SslProperties.class) public class SslAutoConfiguration { + private final ResourceLoader resourceLoader; + private final SslProperties sslProperties; - SslAutoConfiguration(SslProperties sslProperties) { + SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) { + this.resourceLoader = ApplicationResourceLoader.get(resourceLoader); this.sslProperties = sslProperties; } @@ -49,7 +54,7 @@ FileWatcher fileWatcher() { @Bean SslPropertiesBundleRegistrar sslPropertiesSslBundleRegistrar(FileWatcher fileWatcher) { - return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher); + return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher, this.resourceLoader); } @Bean 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 c8f595b0d254..e1cbe6fbc4d6 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 @@ -21,12 +21,14 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; 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; +import org.springframework.core.io.ResourceLoader; /** * A {@link SslBundleRegistrar} that registers SSL bundles based @@ -42,9 +44,12 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar { private final FileWatcher fileWatcher; - SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher) { + private final ResourceLoader resourceLoader; + + SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher, ResourceLoader resourceLoader) { this.properties = properties.getBundle(); this.fileWatcher = fileWatcher; + this.resourceLoader = resourceLoader; } @Override @@ -54,9 +59,9 @@ public void registerBundles(SslBundleRegistry registry) { } private

void registerBundles(SslBundleRegistry registry, Map properties, - Function bundleFactory, Function, Set> watchedPaths) { + BiFunction bundleFactory, Function, Set> watchedPaths) { properties.forEach((bundleName, bundleProperties) -> { - Supplier bundleSupplier = () -> bundleFactory.apply(bundleProperties); + Supplier bundleSupplier = () -> bundleFactory.apply(bundleProperties, this.resourceLoader); try { registry.registerBundle(bundleName, bundleSupplier.get()); if (bundleProperties.isReloadOnUpdate()) { @@ -106,7 +111,7 @@ private Set watchedPaths(String bundleName, List pr try { return properties.stream() .filter(BundleContentProperty::hasValue) - .map(BundleContentProperty::toWatchPath) + .map((content) -> content.toWatchPath(this.resourceLoader)) .collect(Collectors.toSet()); } catch (BundleContentNotWatchableException ex) { 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 2f76a06a8c76..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,6 @@ @AutoConfiguration @EnableConfigurationProperties(TaskExecutionProperties.class) @Import({ TaskExecutorConfigurations.ThreadPoolTaskExecutorBuilderConfiguration.class, - TaskExecutorConfigurations.TaskExecutorBuilderConfiguration.class, TaskExecutorConfigurations.SimpleAsyncTaskExecutorBuilderConfiguration.class, TaskExecutorConfigurations.TaskExecutorConfiguration.class }) public class TaskExecutionAutoConfiguration { 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 6deb352e4801..4f30ab25de33 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 @@ -25,6 +25,7 @@ * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou * @since 2.1.0 */ @ConfigurationProperties("spring.task.execution") @@ -112,6 +113,8 @@ public static class Pool { */ private Duration keepAlive = Duration.ofSeconds(60); + private final Shutdown shutdown = new Shutdown(); + public int getQueueCapacity() { return this.queueCapacity; } @@ -152,6 +155,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 index 9f556708d6e7..b8c2758ca1d8 100644 --- 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 @@ -41,6 +41,7 @@ * * @author Andy Wilkinson * @author Moritz Halbritter + * @author Yanming Zhou */ class TaskExecutorConfigurations { @@ -59,58 +60,19 @@ SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExe @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME }) @ConditionalOnThreading(Threading.PLATFORM) - @SuppressWarnings({ "deprecation", "removal" }) - ThreadPoolTaskExecutor applicationTaskExecutor( - org.springframework.boot.task.TaskExecutorBuilder taskExecutorBuilder, - ObjectProvider threadPoolTaskExecutorBuilderProvider) { - ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder = threadPoolTaskExecutorBuilderProvider - .getIfUnique(); - if (threadPoolTaskExecutorBuilder != null) { - return threadPoolTaskExecutorBuilder.build(); - } - return taskExecutorBuilder.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - @SuppressWarnings("removal") - static class TaskExecutorBuilderConfiguration { - - @Bean - @ConditionalOnMissingBean - @Deprecated(since = "3.2.0", forRemoval = true) - org.springframework.boot.task.TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties, - ObjectProvider taskExecutorCustomizers, - ObjectProvider taskDecorator) { - TaskExecutionProperties.Pool pool = properties.getPool(); - org.springframework.boot.task.TaskExecutorBuilder builder = new org.springframework.boot.task.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()); - TaskExecutionProperties.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; + ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder) { + return threadPoolTaskExecutorBuilder.build(); } } @Configuration(proxyBeanMethods = false) - @SuppressWarnings({ "deprecation", "removal" }) static class ThreadPoolTaskExecutorBuilderConfiguration { @Bean - @ConditionalOnMissingBean({ org.springframework.boot.task.TaskExecutorBuilder.class, - ThreadPoolTaskExecutorBuilder.class }) + @ConditionalOnMissingBean(ThreadPoolTaskExecutorBuilder.class) ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder(TaskExecutionProperties properties, ObjectProvider threadPoolTaskExecutorCustomizers, - ObjectProvider taskExecutorCustomizers, ObjectProvider taskDecorator) { TaskExecutionProperties.Pool pool = properties.getPool(); ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); @@ -119,22 +81,16 @@ ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder(TaskExecutionPropert 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()); - // Apply the deprecated TaskExecutorCustomizers, too - builder = builder.additionalCustomizers(taskExecutorCustomizers.orderedStream().map(this::adapt).toList()); return builder; } - private ThreadPoolTaskExecutorCustomizer adapt( - org.springframework.boot.task.TaskExecutorCustomizer customizer) { - return customizer::customize; - } - } @Configuration(proxyBeanMethods = false) 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 5909153ee8e3..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 +39,6 @@ @AutoConfiguration(after = TaskExecutionAutoConfiguration.class) @EnableConfigurationProperties(TaskSchedulingProperties.class) @Import({ TaskSchedulingConfigurations.ThreadPoolTaskSchedulerBuilderConfiguration.class, - TaskSchedulingConfigurations.TaskSchedulerBuilderConfiguration.class, TaskSchedulingConfigurations.SimpleAsyncTaskSchedulerBuilderConfiguration.class, TaskSchedulingConfigurations.TaskSchedulerConfiguration.class }) public class TaskSchedulingAutoConfiguration { 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 index 6d2ddebf5f38..53ae5c870218 100644 --- 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 @@ -54,50 +54,20 @@ SimpleAsyncTaskScheduler taskSchedulerVirtualThreads(SimpleAsyncTaskSchedulerBui } @Bean - @SuppressWarnings({ "deprecation", "removal" }) @ConditionalOnThreading(Threading.PLATFORM) - ThreadPoolTaskScheduler taskScheduler(org.springframework.boot.task.TaskSchedulerBuilder taskSchedulerBuilder, - ObjectProvider threadPoolTaskSchedulerBuilderProvider) { - ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder = threadPoolTaskSchedulerBuilderProvider - .getIfUnique(); - if (threadPoolTaskSchedulerBuilder != null) { - return threadPoolTaskSchedulerBuilder.build(); - } - return taskSchedulerBuilder.build(); + ThreadPoolTaskScheduler taskScheduler(ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder) { + return threadPoolTaskSchedulerBuilder.build(); } } @Configuration(proxyBeanMethods = false) - @SuppressWarnings({ "deprecation", "removal" }) - static class TaskSchedulerBuilderConfiguration { - - @Bean - @ConditionalOnMissingBean - org.springframework.boot.task.TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties, - ObjectProvider taskSchedulerCustomizers) { - org.springframework.boot.task.TaskSchedulerBuilder builder = new org.springframework.boot.task.TaskSchedulerBuilder(); - builder = builder.poolSize(properties.getPool().getSize()); - TaskSchedulingProperties.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; - } - - } - - @Configuration(proxyBeanMethods = false) - @SuppressWarnings({ "deprecation", "removal" }) static class ThreadPoolTaskSchedulerBuilderConfiguration { @Bean - @ConditionalOnMissingBean({ org.springframework.boot.task.TaskSchedulerBuilder.class, - ThreadPoolTaskSchedulerBuilder.class }) + @ConditionalOnMissingBean(ThreadPoolTaskSchedulerBuilder.class) ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder(TaskSchedulingProperties properties, - ObjectProvider threadPoolTaskSchedulerCustomizers, - ObjectProvider taskSchedulerCustomizers) { + ObjectProvider threadPoolTaskSchedulerCustomizers) { TaskSchedulingProperties.Shutdown shutdown = properties.getShutdown(); ThreadPoolTaskSchedulerBuilder builder = new ThreadPoolTaskSchedulerBuilder(); builder = builder.poolSize(properties.getPool().getSize()); @@ -105,16 +75,9 @@ ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder(TaskSchedulingProp builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); builder = builder.customizers(threadPoolTaskSchedulerCustomizers); - // Apply the deprecated TaskSchedulerCustomizers, too - builder = builder.additionalCustomizers(taskSchedulerCustomizers.orderedStream().map(this::adapt).toList()); return builder; } - private ThreadPoolTaskSchedulerCustomizer adapt( - org.springframework.boot.task.TaskSchedulerCustomizer customizer) { - return customizer::customize; - } - } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java index e03a8274ba94..b1d32a5a2c25 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.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. @@ -86,13 +86,12 @@ public class ThymeleafProperties { private Integer templateResolverOrder; /** - * Comma-separated list of view names (patterns allowed) that can be resolved. + * List of view names (patterns allowed) that can be resolved. */ private String[] viewNames; /** - * Comma-separated list of view names (patterns allowed) that should be excluded from - * resolution. + * List of view names (patterns allowed) that should be excluded from resolution. */ private String[] excludedViewNames; 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 1b5cd099471e..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/PlatformTransactionManagerCustomizer.java +++ /dev/null @@ -1,37 +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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of - * {@link TransactionManagerCustomizer}. - */ -@Deprecated(since = "3.2.0", forRemoval = true) -@FunctionalInterface -public interface PlatformTransactionManagerCustomizer - extends TransactionManagerCustomizer { - -} 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 88f513a3c9d0..3ec362e0fee5 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,7 +22,6 @@ import java.util.List; import org.springframework.boot.util.LambdaSafe; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionManager; /** @@ -32,38 +31,14 @@ * @author Andy Wilkinson * @since 1.5.0 */ -public class TransactionManagerCustomizers { +public final class TransactionManagerCustomizers { private final List> customizers; - /** - * Creates a new {@code TransactionManagerCustomizers} instance containing the given - * {@code customizers}. - * @param customizers the customizers - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of {@link #of(Collection)} - */ - @SuppressWarnings("removal") - @Deprecated(since = "3.2.0", forRemoval = true) - public TransactionManagerCustomizers(Collection> customizers) { - this((customizers != null) ? new ArrayList<>(customizers) - : Collections.>emptyList()); - } - private TransactionManagerCustomizers(List> customizers) { this.customizers = customizers; } - /** - * Customize the given {@code platformTransactionManager}. - * @param platformTransactionManager the platform transaction manager to customize - * @deprecated since 3.2.0 for removal in 3.4.0 in favor of - * {@link #customize(TransactionManager)} - */ - @Deprecated(since = "3.2.0", forRemoval = true) - public void customize(PlatformTransactionManager platformTransactionManager) { - customize((TransactionManager) platformTransactionManager); - } - /** * Customize the given {@code transactionManager}. * @param transactionManager the transaction manager to customize @@ -84,8 +59,8 @@ public void customize(TransactionManager transactionManager) { * @since 3.2.0 */ public static TransactionManagerCustomizers of(Collection> customizers) { - return new TransactionManagerCustomizers((customizers != null) ? new ArrayList<>(customizers) - : Collections.>emptyList()); + 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/jta/JndiJtaConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java index 3db22f4cc775..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,6 @@ import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.transaction.TransactionManager; import org.springframework.transaction.jta.JtaTransactionManager; /** @@ -44,8 +43,7 @@ class JndiJtaConfiguration { JtaTransactionManager transactionManager( ObjectProvider transactionManagerCustomizers) { JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(); - transactionManagerCustomizers - .ifAvailable((customizers) -> customizers.customize((TransactionManager) jtaTransactionManager)); + transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(jtaTransactionManager)); return jtaTransactionManager; } 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 e6c792838d86..ad25fa700f2f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +58,8 @@ public final Validator getTarget() { } @Override - public boolean supports(Class clazz) { - return this.target.supports(clazz); + public boolean supports(Class type) { + return this.target.supports(type); } @Override 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 ae4ea2a4974a..2882b7079cbe 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 @@ -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) @@ -111,7 +113,7 @@ public class ServerProperties { /** * Type of shutdown that the server will support. */ - private Shutdown shutdown = Shutdown.IMMEDIATE; + private Shutdown shutdown = Shutdown.GRACEFUL; @NestedConfigurationProperty private Ssl ssl; @@ -119,6 +121,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(); @@ -190,6 +197,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; } @@ -333,6 +348,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(); @@ -344,6 +364,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; } @@ -441,21 +469,21 @@ public static class Tomcat { private int maxKeepAliveRequests = 100; /** - * Comma-separated list of additional patterns that match jars to ignore for TLD - * scanning. The special '?' and '*' characters can be used in the pattern to - * match one and only one character and zero or more characters respectively. + * List of additional patterns that match jars to ignore for TLD scanning. The + * special '?' and '*' characters can be used in the pattern to match one and only + * one character and zero or more characters respectively. */ private List additionalTldSkipPatterns = new ArrayList<>(); /** - * Comma-separated list of additional unencoded characters that should be allowed - * in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed. + * List of additional unencoded characters that should be allowed in URI paths. + * Only "< > [ \ ] ^ ` { | }" are allowed. */ private List relaxedPathChars = new ArrayList<>(); /** - * Comma-separated list of additional unencoded characters that should be allowed - * in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed. + * List of additional unencoded characters that should be allowed in URI query + * strings. Only "< > [ \ ] ^ ` { | }" are allowed. */ private List relaxedQueryChars = new ArrayList<>(); @@ -465,13 +493,6 @@ public static class Tomcat { */ private Duration connectionTimeout; - /** - * Whether to reject requests with illegal header names or values. - * @deprecated since 2.7.12 for removal in 3.3.0 - */ - @Deprecated(since = "2.7.12", forRemoval = true) // Remove in 3.3 - private boolean rejectIllegalHeader = true; - /** * Static resource configuration. */ @@ -628,17 +649,6 @@ public void setConnectionTimeout(Duration connectionTimeout) { this.connectionTimeout = connectionTimeout; } - @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; - } - public Resource getResource() { return this.resource; } @@ -912,6 +922,12 @@ public static class Threads { */ private int minSpare = 10; + /** + * Maximum capacity of the thread pool's backing queue. This setting only has + * an effect if the value is greater than 0. + */ + private int maxQueueCapacity = 2147483647; + public int getMax() { return this.max; } @@ -928,6 +944,14 @@ public void setMinSpare(int minSpare) { this.minSpare = minSpare; } + public int getMaxQueueCapacity() { + return this.maxQueueCapacity; + } + + public void setMaxQueueCapacity(int maxQueueCapacity) { + this.maxQueueCapacity = maxQueueCapacity; + } + } /** @@ -1111,6 +1135,11 @@ public static class Jetty { */ private DataSize maxHttpFormPostSize = DataSize.ofBytes(200000); + /** + * Maximum number of form keys. + */ + private int maxFormKeys = 1000; + /** * Time that the connection can be idle before it is closed. */ @@ -1143,6 +1172,14 @@ public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) { this.maxHttpFormPostSize = maxHttpFormPostSize; } + public int getMaxFormKeys() { + return this.maxFormKeys; + } + + public void setMaxFormKeys(int maxFormKeys) { + this.maxFormKeys = maxFormKeys; + } + public Duration getConnectionIdleTimeout() { return this.connectionIdleTimeout; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebProperties.java index d0ba50a51cae..4b834cd55c85 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -251,8 +251,7 @@ public static class Content { private boolean enabled; /** - * Comma-separated list of patterns to apply to the content Version - * Strategy. + * List of patterns to apply to the content Version Strategy. */ private String[] paths = new String[] { "/**" }; @@ -293,8 +292,7 @@ public static class Fixed { private boolean enabled; /** - * Comma-separated list of patterns to apply to the fixed Version - * Strategy. + * List of patterns to apply to the fixed Version Strategy. */ private String[] paths = new String[] { "/**" }; 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 index 8fc9dd9663b2..0bdf8fd80ac6 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 @@ import java.util.function.Consumer; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; 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; @@ -32,9 +32,13 @@ */ class AutoConfiguredRestClientSsl implements RestClientSsl { + private final ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder; + private final SslBundles sslBundles; - AutoConfiguredRestClientSsl(SslBundles sslBundles) { + AutoConfiguredRestClientSsl(ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder, + SslBundles sslBundles) { + this.clientHttpRequestFactoryBuilder = clientHttpRequestFactoryBuilder; this.sslBundles = sslBundles; } @@ -46,8 +50,8 @@ public Consumer fromBundle(String bundleName) { @Override public Consumer fromBundle(SslBundle bundle) { return (builder) -> { - ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(bundle); - ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings); + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(bundle); + ClientHttpRequestFactory requestFactory = this.clientHttpRequestFactoryBuilder.build(settings); builder.requestFactory(requestFactory); }; } 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 index 6281d4710422..c2312ce79019 100644 --- 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 @@ -24,10 +24,11 @@ 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.http.client.HttpClientAutoConfiguration; import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; 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; @@ -48,7 +49,8 @@ * @author Moritz Halbritter * @since 3.2.0 */ -@AutoConfiguration(after = { HttpMessageConvertersAutoConfiguration.class, SslAutoConfiguration.class }) +@AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + SslAutoConfiguration.class }) @ConditionalOnClass(RestClient.class) @Conditional(NotReactiveWebApplicationCondition.class) public class RestClientAutoConfiguration { @@ -64,14 +66,21 @@ HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomi @Bean @ConditionalOnMissingBean(RestClientSsl.class) @ConditionalOnBean(SslBundles.class) - AutoConfiguredRestClientSsl restClientSsl(SslBundles sslBundles) { - return new AutoConfiguredRestClientSsl(sslBundles); + AutoConfiguredRestClientSsl restClientSsl( + ObjectProvider> clientHttpRequestFactoryBuilder, SslBundles sslBundles) { + return new AutoConfiguredRestClientSsl( + clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect), sslBundles); } @Bean @ConditionalOnMissingBean - RestClientBuilderConfigurer restClientBuilderConfigurer(ObjectProvider customizerProvider) { + RestClientBuilderConfigurer restClientBuilderConfigurer( + ObjectProvider> clientHttpRequestFactoryBuilder, + ObjectProvider clientHttpRequestFactorySettings, + ObjectProvider customizerProvider) { RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer(); + configurer.setRequestFactoryBuilder(clientHttpRequestFactoryBuilder.getIfAvailable()); + configurer.setRequestFactorySettings(clientHttpRequestFactorySettings.getIfAvailable()); configurer.setRestClientCustomizers(customizerProvider.orderedStream().toList()); return configurer; } @@ -80,9 +89,7 @@ RestClientBuilderConfigurer restClientBuilderConfigurer(ObjectProvider requestFactoryBuilder; + + private ClientHttpRequestFactorySettings requestFactorySettings; + private List customizers; + void setRequestFactoryBuilder(ClientHttpRequestFactoryBuilder requestFactoryBuilder) { + this.requestFactoryBuilder = requestFactoryBuilder; + } + + void setRequestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) { + this.requestFactorySettings = requestFactorySettings; + } + void setRestClientCustomizers(List customizers) { this.customizers = customizers; } @@ -43,6 +57,9 @@ void setRestClientCustomizers(List customizers) { * @return the configured builder */ public RestClient.Builder configure(RestClient.Builder builder) { + ClientHttpRequestFactoryBuilder requestFactoryBuilder = (this.requestFactoryBuilder != null) + ? this.requestFactoryBuilder : ClientHttpRequestFactoryBuilder.detect(); + builder.requestFactory(requestFactoryBuilder.build(this.requestFactorySettings)); applyCustomizers(builder); return 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 index fd892efb4326..9477088eba4c 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,9 @@ import java.util.function.Consumer; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; 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; @@ -38,8 +37,7 @@ * 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}. + * consider using a {@link ClientHttpRequestFactoryBuilder}. * * @author Phillip Webb * @since 3.2.0 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 f6fc73629ae5..a4879185a4fa 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 @@ -23,6 +23,9 @@ 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.http.client.HttpClientAutoConfiguration; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateRequestCustomizer; @@ -32,13 +35,14 @@ import org.springframework.web.client.RestTemplate; /** - * {@link EnableAutoConfiguration Auto-configuration} for {@link RestTemplate}. + * {@link EnableAutoConfiguration Auto-configuration} for {@link RestTemplate} (via + * {@link RestTemplateBuilder}). * * @author Stephane Nicoll * @author Phillip Webb * @since 1.4.0 */ -@AutoConfiguration(after = HttpMessageConvertersAutoConfiguration.class) +@AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) @ConditionalOnClass(RestTemplate.class) @Conditional(NotReactiveWebApplicationCondition.class) public class RestTemplateAutoConfiguration { @@ -46,10 +50,14 @@ public class RestTemplateAutoConfiguration { @Bean @Lazy public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer( + ObjectProvider> clientHttpRequestFactoryBuilder, + ObjectProvider clientHttpRequestFactorySettings, ObjectProvider messageConverters, ObjectProvider restTemplateCustomizers, ObjectProvider> restTemplateRequestCustomizers) { RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer(); + configurer.setRequestFactoryBuilder(clientHttpRequestFactoryBuilder.getIfAvailable()); + configurer.setRequestFactorySettings(clientHttpRequestFactorySettings.getIfAvailable()); configurer.setHttpMessageConverters(messageConverters.getIfUnique()); configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().toList()); configurer.setRestTemplateRequestCustomizers(restTemplateRequestCustomizers.orderedStream().toList()); @@ -60,8 +68,7 @@ public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer( @Lazy @ConditionalOnMissingBean public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) { - RestTemplateBuilder builder = new RestTemplateBuilder(); - return restTemplateBuilderConfigurer.configure(builder); + return restTemplateBuilderConfigurer.configure(new RestTemplateBuilder()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateBuilderConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateBuilderConfigurer.java index 55e3351b85ff..5e09b490d70c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateBuilderConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateBuilderConfigurer.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 @@ import java.util.function.BiFunction; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateRequestCustomizer; @@ -34,12 +36,24 @@ */ public final class RestTemplateBuilderConfigurer { + private ClientHttpRequestFactoryBuilder requestFactoryBuilder; + + private ClientHttpRequestFactorySettings requestFactorySettings; + private HttpMessageConverters httpMessageConverters; private List restTemplateCustomizers; private List> restTemplateRequestCustomizers; + void setRequestFactoryBuilder(ClientHttpRequestFactoryBuilder requestFactoryBuilder) { + this.requestFactoryBuilder = requestFactoryBuilder; + } + + void setRequestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) { + this.requestFactorySettings = requestFactorySettings; + } + void setHttpMessageConverters(HttpMessageConverters httpMessageConverters) { this.httpMessageConverters = httpMessageConverters; } @@ -59,6 +73,12 @@ void setRestTemplateRequestCustomizers(List> re * @return the configured builder */ public RestTemplateBuilder configure(RestTemplateBuilder builder) { + if (this.requestFactoryBuilder != null) { + builder = builder.requestFactoryBuilder(this.requestFactoryBuilder); + } + if (this.requestFactorySettings != null) { + builder = builder.requestFactorySettings(this.requestFactorySettings); + } if (this.httpMessageConverters != null) { builder = builder.messageConverters(this.httpMessageConverters.getConverters()); } 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 371ab5034526..aef7f8036163 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. @@ -34,15 +34,18 @@ import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.embedded.undertow.UndertowDeploymentInfoCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; +import org.springframework.core.task.VirtualThreadTaskExecutor; /** * {@link EnableAutoConfiguration Auto-configuration} for embedded servlet and reactive * web servers customizations. * * @author Phillip Webb + * @author Moritz Halbritter * @since 2.0.0 */ @AutoConfiguration @@ -107,6 +110,12 @@ public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Env return new UndertowWebServerFactoryCustomizer(environment, serverProperties); } + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + UndertowDeploymentInfoCustomizer virtualThreadsUndertowDeploymentInfoCustomizer() { + return (deploymentInfo) -> deploymentInfo.setExecutor(new VirtualThreadTaskExecutor("undertow-")); + } + } /** 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 c12333dc9cfc..7799bcd817e7 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,21 +19,23 @@ import java.time.Duration; import java.util.Arrays; import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Stream; import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.CustomRequestLog; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.RequestLogWriter; -import org.eclipse.jetty.server.Server; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory; -import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.core.Ordered; import org.springframework.core.env.Environment; @@ -83,18 +85,21 @@ public void customize(ConfigurableJettyWebServerFactory factory) { map.from(this.serverProperties::getMaxHttpRequestHeaderSize) .asInt(DataSize::toBytes) .when(this::isPositive) - .to((maxHttpRequestHeaderSize) -> factory - .addServerCustomizers(new MaxHttpRequestHeaderSizeCustomizer(maxHttpRequestHeaderSize))); + .to(customizeHttpConfigurations(factory, HttpConfiguration::setRequestHeaderSize)); map.from(properties::getMaxHttpResponseHeaderSize) .asInt(DataSize::toBytes) .when(this::isPositive) - .to((maxHttpResponseHeaderSize) -> factory - .addServerCustomizers(new MaxHttpResponseHeaderSizeCustomizer(maxHttpResponseHeaderSize))); + .to(customizeHttpConfigurations(factory, HttpConfiguration::setResponseHeaderSize)); map.from(properties::getMaxHttpFormPostSize) .asInt(DataSize::toBytes) .when(this::isPositive) - .to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize)); - map.from(properties::getConnectionIdleTimeout).to((idleTimeout) -> customizeIdleTimeout(factory, idleTimeout)); + .to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormContentSize)); + map.from(properties::getMaxFormKeys) + .when(this::isPositive) + .to(customizeServletContextHandler(factory, ServletContextHandler::setMaxFormKeys)); + map.from(properties::getConnectionIdleTimeout) + .as(Duration::toMillis) + .to(customizeAbstractConnectors(factory, AbstractConnector::setIdleTimeout)); map.from(properties::getAccesslog) .when(ServerProperties.Jetty.Accesslog::isEnabled) .to((accesslog) -> customizeAccessLog(factory, accesslog)); @@ -112,43 +117,63 @@ private boolean getOrDeduceUseForwardHeaders() { return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE); } - private void customizeIdleTimeout(ConfigurableJettyWebServerFactory factory, Duration connectionTimeout) { - factory.addServerCustomizers((server) -> { - for (org.eclipse.jetty.server.Connector connector : server.getConnectors()) { - if (connector instanceof AbstractConnector abstractConnector) { - abstractConnector.setIdleTimeout(connectionTimeout.toMillis()); - } - } + private Consumer customizeHttpConfigurations(ConfigurableJettyWebServerFactory factory, + BiConsumer action) { + return customizeConnectionFactories(factory, HttpConfiguration.ConnectionFactory.class, + (connectionFactory, value) -> action.accept(connectionFactory.getHttpConfiguration(), value)); + } + + private Consumer customizeConnectionFactories(ConfigurableJettyWebServerFactory factory, + Class connectionFactoryType, BiConsumer action) { + return customizeConnectors(factory, Connector.class, (connector, value) -> { + Stream connectionFactories = connector.getConnectionFactories().stream(); + forEach(connectionFactories, connectionFactoryType, action, value); }); } - private void customizeMaxHttpFormPostSize(ConfigurableJettyWebServerFactory factory, int maxHttpFormPostSize) { - factory.addServerCustomizers(new JettyServerCustomizer() { + private Consumer customizeAbstractConnectors(ConfigurableJettyWebServerFactory factory, + BiConsumer action) { + return customizeConnectors(factory, AbstractConnector.class, action); + } - @Override - public void customize(Server server) { - setHandlerMaxHttpFormPostSize(server.getHandlers()); - } + private Consumer customizeConnectors(ConfigurableJettyWebServerFactory factory, Class connectorType, + BiConsumer action) { + return (value) -> factory.addServerCustomizers((server) -> { + Stream connectors = Arrays.stream(server.getConnectors()); + forEach(connectors, connectorType, action, value); + }); + } - private void setHandlerMaxHttpFormPostSize(List handlers) { - for (Handler handler : handlers) { - setHandlerMaxHttpFormPostSize(handler); - } - } + private Consumer customizeServletContextHandler(ConfigurableJettyWebServerFactory factory, + BiConsumer action) { + return customizeHandlers(factory, ServletContextHandler.class, action); + } - 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 Consumer customizeHandlers(ConfigurableJettyWebServerFactory factory, Class handlerType, + BiConsumer action) { + return (value) -> factory.addServerCustomizers((server) -> { + List handlers = server.getHandlers(); + forEachHandler(handlers, handlerType, action, value); + }); + } + + @SuppressWarnings("unchecked") + private void forEachHandler(List handlers, Class handlerType, BiConsumer action, V value) { + for (Handler handler : handlers) { + if (handlerType.isInstance(handler)) { + action.accept((H) handler, value); + } + if (handler instanceof Handler.Wrapper wrapper) { + forEachHandler(wrapper.getHandlers(), handlerType, action, value); } + if (handler instanceof Handler.Collection collection) { + forEachHandler(collection.getHandlers(), handlerType, action, value); + } + } + } - }); + private void forEach(Stream elements, Class type, BiConsumer action, V value) { + elements.filter(type::isInstance).map(type::cast).forEach((element) -> action.accept(element, value)); } private void customizeAccessLog(ConfigurableJettyWebServerFactory factory, @@ -176,61 +201,10 @@ private String getLogFormat(ServerProperties.Jetty.Accesslog properties) { if (properties.getCustomFormat() != null) { return properties.getCustomFormat(); } - else if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) { + if (ServerProperties.Jetty.Accesslog.FORMAT.EXTENDED_NCSA.equals(properties.getFormat())) { return CustomRequestLog.EXTENDED_NCSA_FORMAT; } return CustomRequestLog.NCSA_FORMAT; } - private static class MaxHttpRequestHeaderSizeCustomizer implements JettyServerCustomizer { - - private final int maxRequestHeaderSize; - - MaxHttpRequestHeaderSizeCustomizer(int maxRequestHeaderSize) { - this.maxRequestHeaderSize = maxRequestHeaderSize; - } - - @Override - public void customize(Server server) { - Arrays.stream(server.getConnectors()).forEach(this::customize); - } - - private void customize(org.eclipse.jetty.server.Connector connector) { - connector.getConnectionFactories().forEach(this::customize); - } - - private void customize(ConnectionFactory factory) { - if (factory instanceof HttpConfiguration.ConnectionFactory) { - ((HttpConfiguration.ConnectionFactory) factory).getHttpConfiguration() - .setRequestHeaderSize(this.maxRequestHeaderSize); - } - } - - } - - private static class MaxHttpResponseHeaderSizeCustomizer implements JettyServerCustomizer { - - private final int maxResponseHeaderSize; - - MaxHttpResponseHeaderSizeCustomizer(int maxResponseHeaderSize) { - this.maxResponseHeaderSize = maxResponseHeaderSize; - } - - @Override - public void customize(Server server) { - Arrays.stream(server.getConnectors()).forEach(this::customize); - } - - private void customize(org.eclipse.jetty.server.Connector connector) { - connector.getConnectionFactories().forEach(this::customize); - } - - private void customize(ConnectionFactory factory) { - if (factory instanceof HttpConfiguration.ConnectionFactory httpConnectionFactory) { - httpConnectionFactory.getHttpConfiguration().setResponseHeaderSize(this.maxResponseHeaderSize); - } - } - - } - } 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 1c357d2d0ac0..6feadf329bf0 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. @@ -97,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) @@ -142,8 +145,6 @@ public void customize(ConfigurableTomcatWebServerFactory factory) { .as(this::joinCharacters) .whenHasText() .to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars)); - map.from(properties::isRejectIllegalHeader) - .to((rejectIllegalHeader) -> customizeRejectIllegalHeader(factory, rejectIllegalHeader)); customizeStaticResources(factory); customizeErrorReportValve(this.serverProperties.getError(), factory); } @@ -152,6 +153,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); @@ -201,16 +217,6 @@ private void customizeRelaxedQueryChars(ConfigurableTomcatWebServerFactory facto factory.addConnectorCustomizers((connector) -> connector.setProperty("relaxedQueryChars", relaxedChars)); } - @SuppressWarnings("deprecation") - private void customizeRejectIllegalHeader(ConfigurableTomcatWebServerFactory factory, boolean rejectIllegalHeader) { - factory.addConnectorCustomizers((connector) -> { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractHttp11Protocol protocol) { - protocol.setRejectIllegalHeader(rejectIllegalHeader); - } - }); - } - private String joinCharacters(List content) { return content.stream().map(String::valueOf).collect(Collectors.joining()); } @@ -252,16 +258,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/embedded/UndertowWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java index fc9640ba10b4..c71f67929f51 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java @@ -77,8 +77,7 @@ public int getOrder() { public void customize(ConfigurableUndertowWebServerFactory factory) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); ServerOptions options = new ServerOptions(factory); - ServerProperties properties = this.serverProperties; - map.from(properties::getMaxHttpRequestHeaderSize) + map.from(this.serverProperties::getMaxHttpRequestHeaderSize) .asInt(DataSize::toBytes) .when(this::isPositive) .to(options.option(UndertowOptions.MAX_HEADER_SIZE)); 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/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index cab4dd72b436..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 @@ -338,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; } 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/WebSessionIdResolverAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebSessionIdResolverAutoConfiguration.java index 0ef46418d57c..797910b8ea37 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebSessionIdResolverAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebSessionIdResolverAutoConfiguration.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. @@ -76,6 +76,7 @@ private void initializeCookie(ResponseCookieBuilder builder) { map.from(cookie::getHttpOnly).to(builder::httpOnly); map.from(cookie::getSecure).to(builder::secure); map.from(cookie::getMaxAge).to(builder::maxAge); + map.from(cookie::getPartitioned).to(builder::partitioned); map.from(getSameSite(cookie)).to(builder::sameSite); } 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 6be5700cf57f..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 @@ -54,6 +54,7 @@ * * @author Brian Clozel * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 * @see ErrorAttributes */ @@ -168,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); 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 072a049baa4d..01b615615ef0 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 @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.web.WebProperties.Resources; 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.context.ApplicationContext; import org.springframework.http.HttpStatus; @@ -76,6 +77,7 @@ * * @author Brian Clozel * @author Scott Frederick + * @author Moritz Halbritter * @since 2.0.0 */ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { @@ -93,6 +95,8 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa private static final ErrorAttributeOptions ONLY_STATUS = ErrorAttributeOptions.of(Include.STATUS); + private static final DefaultErrorAttributes defaultErrorAttributes = new DefaultErrorAttributes(); + private final ErrorProperties errorProperties; /** @@ -120,8 +124,8 @@ protected RouterFunction getRoutingFunction(ErrorAttributes erro * @return a {@code Publisher} of the HTTP response */ protected Mono renderErrorView(ServerRequest request) { - int status = getHttpStatus(getErrorAttributes(request, ONLY_STATUS)); Map errorAttributes = getErrorAttributes(request, MediaType.TEXT_HTML); + int status = getHttpStatus(request, errorAttributes); ServerResponse.BodyBuilder responseBody = ServerResponse.status(status).contentType(TEXT_HTML_UTF8); return Flux.just(getData(status).toArray(new String[] {})) .flatMap((viewName) -> renderErrorView(viewName, responseBody, errorAttributes)) @@ -147,8 +151,8 @@ private List getData(int errorStatus) { * @return a {@code Publisher} of the HTTP response */ protected Mono renderErrorResponse(ServerRequest request) { - int status = getHttpStatus(getErrorAttributes(request, ONLY_STATUS)); Map errorAttributes = getErrorAttributes(request, MediaType.ALL); + int status = getHttpStatus(request, errorAttributes); return ServerResponse.status(status) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(errorAttributes)); @@ -172,6 +176,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; } @@ -185,7 +190,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; }; } @@ -199,7 +204,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; }; } @@ -213,10 +218,30 @@ 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; + }; + } + + private int getHttpStatus(ServerRequest request, Map errorAttributes) { + return getHttpStatus(errorAttributes.containsKey("status") ? errorAttributes + : defaultErrorAttributes.getErrorAttributes(request, ONLY_STATUS)); + } + /** * Get the HTTP error status information from the error map. * @param errorAttributes the current error information 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 f8fc51bc0133..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,18 +89,11 @@ public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); - configureThrowExceptionIfNoHandlerFound(webMvcProperties, dispatcherServlet); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; } - @SuppressWarnings({ "deprecation", "removal" }) - private void configureThrowExceptionIfNoHandlerFound(WebMvcProperties webMvcProperties, - DispatcherServlet dispatcherServlet) { - dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); - } - @Bean @ConditionalOnBean(MultipartResolver.class) @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java index 302099298c1e..aaf630b1ad0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPath.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. @@ -74,7 +74,7 @@ default String getPrefix() { * @return the path as a servlet URL mapping */ default String getServletUrlMapping() { - if (getPath().equals("") || getPath().equals("/")) { + if (getPath().isEmpty() || getPath().equals("/")) { return "/"; } if (getPath().contains("*")) { 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 f5fff60f90a6..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 @@ -95,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; @@ -469,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); 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 85328f5d13d8..53d6bbfdd1af 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 @@ -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; @@ -62,14 +61,6 @@ public class WebMvcProperties { */ private boolean publishRequestHandledEvents = true; - /** - * Whether a "NoHandlerFoundException" should be thrown if no Handler was found to - * process a request. - * @deprecated since 3.2.0 for removal in 3.4.0 - */ - @Deprecated(since = "3.2.0", forRemoval = true) - private boolean throwExceptionIfNoHandlerFound = true; - /** * Whether logging of (potentially sensitive) request details at DEBUG and TRACE level * is allowed. @@ -124,19 +115,6 @@ public void setPublishRequestHandledEvents(boolean publishRequestHandledEvents) this.publishRequestHandledEvents = publishRequestHandledEvents; } - @Deprecated(since = "3.2.0", forRemoval = true) - @DeprecatedConfigurationProperty( - reason = "DispatcherServlet property is deprecated for removal and should no longer need to be configured", - since = "3.2.0") - public boolean isThrowExceptionIfNoHandlerFound() { - return this.throwExceptionIfNoHandlerFound; - } - - @Deprecated(since = "3.2.0", forRemoval = true) - public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) { - this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound; - } - public boolean isLogRequestDetails() { return this.logRequestDetails; } @@ -259,7 +237,7 @@ public void setLoadOnStartup(int loadOnStartup) { } public String getServletMapping() { - if (this.path.equals("") || this.path.equals("/")) { + if (this.path.isEmpty() || this.path.equals("/")) { return "/"; } if (this.path.endsWith("/")) { 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/webservices/WebServicesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/WebServicesAutoConfiguration.java index 77f7a6b9e115..bed9af37dacb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/WebServicesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/WebServicesAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,7 +124,7 @@ private void registerBeans(String location, String pattern, Class type, Function beanSupplier, BeanDefinitionRegistry registry) { for (Resource resource : getResources(location, pattern)) { BeanDefinition beanDefinition = BeanDefinitionBuilder - .genericBeanDefinition(type, () -> beanSupplier.apply(resource)) + .rootBeanDefinition(type, () -> beanSupplier.apply(resource)) .getBeanDefinition(); registry.registerBeanDefinition(StringUtils.stripFilenameExtension(resource.getFilename()), beanDefinition); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfiguration.java index 518347f8af3f..998051c68e62 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 @@ 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.http.client.HttpClientAutoConfiguration; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.webservices.client.WebServiceMessageSenderFactory; import org.springframework.boot.webservices.client.WebServiceTemplateBuilder; import org.springframework.boot.webservices.client.WebServiceTemplateCustomizer; import org.springframework.context.annotation.Bean; @@ -36,20 +40,35 @@ * @author Dmytro Nosan * @since 2.1.0 */ -@AutoConfiguration +@AutoConfiguration(after = HttpClientAutoConfiguration.class) @ConditionalOnClass({ WebServiceTemplate.class, Unmarshaller.class, Marshaller.class }) public class WebServiceTemplateAutoConfiguration { + @Bean + @ConditionalOnMissingBean + public WebServiceMessageSenderFactory webServiceHttpMessageSenderFactory( + ObjectProvider> clientHttpRequestFactoryBuilder, + ObjectProvider clientHttpRequestFactorySettings) { + return WebServiceMessageSenderFactory.http( + clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect), + clientHttpRequestFactorySettings.getIfAvailable()); + } + @Bean @ConditionalOnMissingBean public WebServiceTemplateBuilder webServiceTemplateBuilder( + ObjectProvider httpWebServiceMessageSenderBuilder, ObjectProvider webServiceTemplateCustomizers) { - WebServiceTemplateBuilder builder = new WebServiceTemplateBuilder(); + WebServiceTemplateBuilder templateBuilder = new WebServiceTemplateBuilder(); + WebServiceMessageSenderFactory httpMessageSenderFactory = httpWebServiceMessageSenderBuilder.getIfAvailable(); + if (httpMessageSenderFactory != null) { + templateBuilder = templateBuilder.httpMessageSenderFactory(httpMessageSenderFactory); + } List customizers = webServiceTemplateCustomizers.orderedStream().toList(); if (!customizers.isEmpty()) { - builder = builder.customizers(customizers); + templateBuilder = templateBuilder.customizers(customizers); } - return builder; + return templateBuilder; } } 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 b65ed91535ed..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +64,19 @@ 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 @@ -74,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/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 86ae36361276..44ba87e00956 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 @@ -37,18 +37,6 @@ "level": "error" } }, - { - "name": "server.error.include-binding-errors", - "defaultValue": "never" - }, - { - "name": "server.error.include-message", - "defaultValue": "never" - }, - { - "name": "server.error.include-stacktrace", - "defaultValue": "never" - }, { "name": "server.http2.enabled", "description": "Whether to enable HTTP/2 support, if the current environment supports it.", @@ -68,10 +56,6 @@ "level": "error" } }, - { - "name": "server.jetty.accesslog.format", - "defaultValue": "ncsa" - }, { "name": "server.jetty.accesslog.locale", "deprecation": { @@ -159,6 +143,10 @@ "name": "server.reactive.session.cookie.name", "description": "Name for the cookie." }, + { + "name": "server.reactive.session.cookie.partitioned", + "description": "Whether the generated cookie carries the Partitioned attribute." + }, { "name": "server.reactive.session.cookie.path", "description": "Path of the cookie." @@ -245,6 +233,10 @@ "name": "server.servlet.session.cookie.name", "description": "Name of the cookie." }, + { + "name": "server.servlet.session.cookie.partitioned", + "description": "Whether the generated cookie carries the Partitioned attribute." + }, { "name": "server.servlet.session.cookie.path", "description": "Path of the cookie." @@ -275,10 +267,6 @@ "name": "server.servlet.session.tracking-modes", "description": "Session tracking modes." }, - { - "name": "server.shutdown", - "defaultValue": "immediate" - }, { "name": "server.ssl.bundle", "description": "The name of a configured SSL bundle." @@ -337,6 +325,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." @@ -369,6 +361,12 @@ "level": "error" } }, + { + "name": "server.tomcat.reject-illegal-header", + "deprecation": { + "level": "error" + } + }, { "name": "server.undertow.buffers-per-region", "type": "java.lang.Integer", @@ -491,10 +489,6 @@ "level": "error" } }, - { - "name": "spring.batch.jdbc.initialize-schema", - "defaultValue": "embedded" - }, { "name": "spring.batch.job.enabled", "type": "java.lang.Boolean", @@ -670,6 +664,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", @@ -1027,10 +1041,6 @@ "name": "spring.data.mongodb.uri", "defaultValue": "mongodb://localhost/test" }, - { - "name": "spring.data.mongodb.uuid-representation", - "defaultValue": "java-legacy" - }, { "name": "spring.data.neo4j.auto-index", "description": "Auto index mode.", @@ -1129,10 +1139,6 @@ "level": "error" } }, - { - "name": "spring.data.rest.detection-strategy", - "defaultValue": "default" - }, { "name" : "spring.datasource.continue-on-error", "type" : "java.lang.Boolean", @@ -1315,6 +1321,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", @@ -1371,6 +1388,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", @@ -1406,7 +1431,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "reason": "Flyway Teams only." + "reason": "Removed in Flyway 10" } }, { @@ -1433,6 +1458,34 @@ "reason": "Removed in the open source release of Flyway 7.12." } }, + { + "name": "spring.freemarker.allow-request-override", + "description": "Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name. Only supported with Spring MVC." + }, + { + "name": "spring.freemarker.allow-session-override", + "description": "Whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name. Only supported with Spring MVC." + }, + { + "name": "spring.freemarker.cache", + "description": "Whether to enable template caching. Only supported with Spring MVC." + }, + { + "name": "spring.freemarker.content-type", + "description": "Content-Type value. Only supported with Spring MVC." + }, + { + "name": "spring.freemarker.expose-request-attributes", + "description": "Whether all request attributes should be added to the model prior to merging with the template. Only supported with Spring MVC." + }, + { + "name": "spring.freemarker.expose-session-attributes", + "description": "Whether all HttpSession attributes should be added to the model prior to merging with the template. Only supported with Spring MVC." + }, + { + "name": "spring.freemarker.expose-spring-macro-helpers", + "description": "Whether to expose a RequestContext for use by Spring's macro library, under the name \"springMacroRequestContext\". Only supported with Spring MVC." + }, { "name": "spring.freemarker.prefix", "defaultValue": "" @@ -1543,6 +1596,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" @@ -1551,10 +1625,6 @@ "name": "spring.info.git.location", "defaultValue": "classpath:git.properties" }, - { - "name": "spring.integration.jdbc.initialize-schema", - "defaultValue": "embedded" - }, { "name": "spring.jackson.constructor-detector", "defaultValue": "default" @@ -1571,22 +1641,6 @@ "level": "error" } }, - { - "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" - }, { "name": "spring.jpa.hibernate.use-new-id-generator-mappings", "type": "java.lang.Boolean", @@ -1767,10 +1821,6 @@ "level": "error" } }, - { - "name": "spring.kafka.consumer.isolation-level", - "defaultValue": "read-uncommitted" - }, { "name": "spring.kafka.consumer.ssl.keystore-location", "type": "org.springframework.core.io.Resource", @@ -1807,10 +1857,6 @@ "level": "error" } }, - { - "name": "spring.kafka.jaas.control-flag", - "defaultValue": "required" - }, { "name": "spring.kafka.listener.only-log-record-metadata", "type": "java.lang.Boolean", @@ -1821,10 +1867,6 @@ "level": "error" } }, - { - "name": "spring.kafka.listener.type", - "defaultValue": "single" - }, { "name": "spring.kafka.producer.ssl.keystore-location", "type": "org.springframework.core.io.Resource", @@ -1901,10 +1943,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", @@ -1930,6 +1981,10 @@ "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.", @@ -1937,6 +1992,12 @@ "type": "java.lang.Boolean", "defaultValue": false }, + { + "name": "spring.messages.basename", + "defaultValue": [ + "messages" + ] + }, { "name": "spring.mustache.prefix", "defaultValue": "classpath:/templates/" @@ -2017,32 +2078,21 @@ } }, { - "name": "spring.mvc.pathmatch.matching-strategy", - "defaultValue": "path-pattern-parser" - }, - { - "name": "spring.neo4j.security.trust-strategy", - "defaultValue": "trust-system-ca-signed-certificates" + "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.uri", "defaultValue": "bolt://localhost:7687" }, { - "name": "spring.pulsar.consumer.subscription.initial-position", - "defaultValue": "latest" - }, - { - "name": "spring.pulsar.consumer.subscription.mode", - "defaultValue": "durable" - }, - { - "name": "spring.pulsar.consumer.subscription.topics-mode", - "defaultValue": "persistentonly" - }, - { - "name": "spring.pulsar.consumer.subscription.type", - "defaultValue": "exclusive" + "name": "spring.pulsar.defaults.topic.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable default tenant and namespace support for topics.", + "defaultValue": true }, { "name": "spring.pulsar.function.enabled", @@ -2050,24 +2100,12 @@ "description": "Whether to enable function support.", "defaultValue": true }, - { - "name": "spring.pulsar.producer.access-mode", - "defaultValue": "shared" - }, { "name": "spring.pulsar.producer.cache.enabled", "type": "java.lang.Boolean", "description": "Whether to enable caching in the PulsarProducerFactory.", "defaultValue": true }, - { - "name": "spring.pulsar.producer.hashing-scheme", - "defaultValue": "javastringhash" - }, - { - "name": "spring.pulsar.producer.message-routing-mode", - "defaultValue": "roundrobinpartition" - }, { "name": "spring.quartz.jdbc.comment-prefix", "defaultValue": [ @@ -2075,30 +2113,10 @@ "--" ] }, - { - "name": "spring.quartz.jdbc.initialize-schema", - "defaultValue": "embedded" - }, - { - "name": "spring.quartz.job-store-type", - "defaultValue": "memory" - }, { "name": "spring.quartz.scheduler-name", "defaultValue": "quartzScheduler" }, - { - "name": "spring.r2dbc.pool.validation-depth", - "defaultValue": "local" - }, - { - "name": "spring.rabbitmq.address-shuffle-mode", - "defaultValue": "none" - }, - { - "name": "spring.rabbitmq.cache.connection.mode", - "defaultValue": "channel" - }, { "name": "spring.rabbitmq.dynamic", "type": "java.lang.Boolean", @@ -2112,10 +2130,6 @@ "level": "error" } }, - { - "name": "spring.rabbitmq.listener.type", - "defaultValue": "simple" - }, { "name": "spring.rabbitmq.publisher-confirms", "type": "java.lang.Boolean", @@ -2131,10 +2145,6 @@ "level": "error" } }, - { - "name": "spring.reactor.context-propagation", - "defaultValue": "limited" - }, { "name": "spring.reactor.stacktrace-mode.enabled", "description": "Whether Reactor should collect stacktrace information at runtime.", @@ -2652,6 +2662,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." @@ -2676,10 +2690,6 @@ "name": "spring.rsocket.server.ssl.trust-store-type", "description": "Type of the trust store." }, - { - "name": "spring.rsocket.server.transport", - "defaultValue": "tcp" - }, { "name": "spring.security.filter.dispatcher-types", "defaultValue": [ @@ -2702,46 +2712,10 @@ "level": "error" } }, - { - "name": "spring.session.hazelcast.flush-mode", - "defaultValue": "on-save" - }, - { - "name": "spring.session.hazelcast.save-mode", - "defaultValue": "on-set-attribute" - }, - { - "name": "spring.session.jdbc.flush-mode", - "defaultValue": "on-save" - }, - { - "name": "spring.session.jdbc.initialize-schema", - "defaultValue": "embedded" - }, - { - "name": "spring.session.jdbc.save-mode", - "defaultValue": "on-set-attribute" - }, { "name": "spring.session.redis.cleanup-cron", "defaultValue": "0 * * * * *" }, - { - "name": "spring.session.redis.configure-action", - "defaultValue": "notify-keyspace-events" - }, - { - "name": "spring.session.redis.flush-mode", - "defaultValue": "on-save" - }, - { - "name": "spring.session.redis.repository-type", - "defaultValue": "default" - }, - { - "name": "spring.session.redis.save-mode", - "defaultValue": "on-set-attribute" - }, { "name": "spring.session.servlet.filter-dispatcher-types", "defaultValue": [ @@ -2760,10 +2734,6 @@ "level": "warning" } }, - { - "name": "spring.sql.init.mode", - "defaultValue": "embedded" - }, { "name": "spring.threads.virtual.enabled", "type": "java.lang.Boolean", @@ -2797,10 +2767,6 @@ "name": "spring.thymeleaf.suffix", "defaultValue": ".html" }, - { - "name": "spring.web.locale-resolver", - "defaultValue": "accept-header" - }, { "name": "spring.webflux.hiddenmethod.filter.enabled", "type": "java.lang.Boolean", 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 38fe003d37fd..05740b676a3c 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 @@ -62,8 +62,8 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration +org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration 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 @@ -98,6 +98,7 @@ 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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java index b74a7cb99339..41ced26ff2ba 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java @@ -16,15 +16,22 @@ package org.springframework.boot.autoconfigure; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -34,6 +41,8 @@ import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; import org.springframework.boot.context.annotation.ImportCandidates; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DeferredImportSelector.Group; +import org.springframework.context.annotation.DeferredImportSelector.Group.Entry; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.mock.env.MockEnvironment; @@ -50,7 +59,7 @@ */ class AutoConfigurationImportSelectorTests { - private final TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector(); + private final TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector(null); private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory(); @@ -60,9 +69,7 @@ class AutoConfigurationImportSelectorTests { @BeforeEach void setup() { - this.importSelector.setBeanFactory(this.beanFactory); - this.importSelector.setEnvironment(this.environment); - this.importSelector.setResourceLoader(new DefaultResourceLoader()); + setupImportSelector(this.importSelector); } @Test @@ -151,6 +158,17 @@ void combinedExclusionsAreApplied() { ThymeleafAutoConfiguration.class.getName()); } + @Test + void removedExclusionsAreApplied() { + TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector( + TestAutoConfiguration.class); + setupImportSelector(importSelector); + AnnotationMetadata metadata = AnnotationMetadata.introspect(BasicEnableAutoConfiguration.class); + assertThat(importSelector.selectImports(metadata)).contains(ReplacementAutoConfiguration.class.getName()); + this.environment.setProperty("spring.autoconfigure.exclude", DeprecatedAutoConfiguration.class.getName()); + assertThat(importSelector.selectImports(metadata)).doesNotContain(ReplacementAutoConfiguration.class.getName()); + } + @Test void nonAutoConfigurationClassExclusionsShouldThrowException() { assertThatIllegalStateException() @@ -208,6 +226,22 @@ void getExclusionFilterReuseFilters() { assertThat(this.importSelector.getExclusionFilter().test("com.example.C")).isTrue(); } + @Test + void soringConsidersReplacements() { + TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector( + TestAutoConfiguration.class); + setupImportSelector(importSelector); + AnnotationMetadata metadata = AnnotationMetadata.introspect(BasicEnableAutoConfiguration.class); + assertThat(importSelector.selectImports(metadata)).containsExactly( + AfterDeprecatedAutoConfiguration.class.getName(), ReplacementAutoConfiguration.class.getName()); + Group group = BeanUtils.instantiateClass(importSelector.getImportGroup()); + ((BeanFactoryAware) group).setBeanFactory(this.beanFactory); + group.process(metadata, importSelector); + Stream imports = StreamSupport.stream(group.selectImports().spliterator(), false); + assertThat(imports.map(Entry::getImportClassName)).containsExactly(ReplacementAutoConfiguration.class.getName(), + AfterDeprecatedAutoConfiguration.class.getName()); + } + private String[] selectImports(Class source) { return this.importSelector.selectImports(AnnotationMetadata.introspect(source)); } @@ -216,10 +250,20 @@ private List getAutoConfigurationClassNames() { return ImportCandidates.load(AutoConfiguration.class, getClass().getClassLoader()).getCandidates(); } + private void setupImportSelector(TestAutoConfigurationImportSelector importSelector) { + importSelector.setBeanFactory(this.beanFactory); + importSelector.setEnvironment(this.environment); + importSelector.setResourceLoader(new DefaultResourceLoader()); + } + private final class TestAutoConfigurationImportSelector extends AutoConfigurationImportSelector { private AutoConfigurationImportEvent lastEvent; + TestAutoConfigurationImportSelector(Class autoConfigurationAnnotation) { + super(autoConfigurationAnnotation); + } + @Override protected List getAutoConfigurationImportFilters() { return AutoConfigurationImportSelectorTests.this.filters; @@ -320,4 +364,23 @@ private final class SpringBootApplicationWithClassNameExclusions { } + static class DeprecatedAutoConfiguration { + + } + + static class ReplacementAutoConfiguration { + + } + + @AutoConfigureAfter(DeprecatedAutoConfiguration.class) + static class AfterDeprecatedAutoConfiguration { + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface TestAutoConfiguration { + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReplacementsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReplacementsTests.java new file mode 100644 index 000000000000..30ae61950a18 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationReplacementsTests.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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AutoConfigurationReplacements}. + * + * @author Phillip Webb + */ +class AutoConfigurationReplacementsTests { + + private final AutoConfigurationReplacements replacements = AutoConfigurationReplacements + .load(TestAutoConfigurationReplacements.class, null); + + @Test + void replaceWhenMatchReplacesClassName() { + assertThat(this.replacements.replace("com.example.A1")).isEqualTo("com.example.A2"); + } + + @Test + void replaceWhenNoMatchReturnsOriginalClassName() { + assertThat(this.replacements.replace("com.example.Z1")).isEqualTo("com.example.Z1"); + } + + @Test + void replaceAllReplacesAllMatching() { + Set classNames = new LinkedHashSet<>( + List.of("com.example.A1", "com.example.B1", "com.example.Y1", "com.example.Z1")); + assertThat(this.replacements.replaceAll(classNames)).containsExactly("com.example.A2", "com.example.B2", + "com.example.Y1", "com.example.Z1"); + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface TestAutoConfigurationReplacements { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java index b9bb92d4663f..9352bbc7f3b2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.util.Map; import java.util.Properties; import java.util.Set; +import java.util.function.UnaryOperator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -62,10 +63,14 @@ class AutoConfigurationSorterTests { private static final String A3 = AutoConfigureA3.class.getName(); + private static final String A_WITH_REPLACED = AutoConfigureAWithReplaced.class.getName(); + private static final String B = AutoConfigureB.class.getName(); private static final String B2 = AutoConfigureB2.class.getName(); + private static final String B_WITH_REPLACED = AutoConfigureBWithReplaced.class.getName(); + private static final String C = AutoConfigureC.class.getName(); private static final String D = AutoConfigureD.class.getName(); @@ -86,30 +91,33 @@ class AutoConfigurationSorterTests { private static final String Z2 = AutoConfigureZ2.class.getName(); + private static final UnaryOperator REPLACEMENT_MAPPER = (name) -> name.replace("Deprecated", ""); + private AutoConfigurationSorter sorter; private AutoConfigurationMetadata autoConfigurationMetadata = mock(AutoConfigurationMetadata.class); @BeforeEach void setup() { - this.sorter = new AutoConfigurationSorter(new SkipCycleMetadataReaderFactory(), this.autoConfigurationMetadata); + this.sorter = new AutoConfigurationSorter(new SkipCycleMetadataReaderFactory(), this.autoConfigurationMetadata, + REPLACEMENT_MAPPER); } @Test void byOrderAnnotation() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(LOWEST, HIGHEST, DEFAULT)); + List actual = getInPriorityOrder(LOWEST, HIGHEST, DEFAULT); assertThat(actual).containsExactly(HIGHEST, DEFAULT, LOWEST); } @Test void byAutoConfigureAfter() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C)); + List actual = getInPriorityOrder(A, B, C); assertThat(actual).containsExactly(C, B, A); } @Test void byAutoConfigureAfterAliasFor() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A3, B2, C)); + List actual = getInPriorityOrder(A3, B2, C); assertThat(actual).containsExactly(C, B2, A3); } @@ -117,20 +125,26 @@ void byAutoConfigureAfterAliasFor() { void byAutoConfigureAfterAliasForWithProperties() throws Exception { MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(); this.autoConfigurationMetadata = getAutoConfigurationMetadata(A3, B2, C); - this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata); - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A3, B2, C)); + this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER); + List actual = getInPriorityOrder(A3, B2, C); assertThat(actual).containsExactly(C, B2, A3); } + @Test + void byAutoConfigureAfterWithDeprecated() { + List actual = getInPriorityOrder(A_WITH_REPLACED, B_WITH_REPLACED, C); + assertThat(actual).containsExactly(C, B_WITH_REPLACED, A_WITH_REPLACED); + } + @Test void byAutoConfigureBefore() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z)); + List actual = getInPriorityOrder(X, Y, Z); assertThat(actual).containsExactly(Z, Y, X); } @Test void byAutoConfigureBeforeAliasFor() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y2, Z2)); + List actual = getInPriorityOrder(X, Y2, Z2); assertThat(actual).containsExactly(Z2, Y2, X); } @@ -138,45 +152,46 @@ void byAutoConfigureBeforeAliasFor() { void byAutoConfigureBeforeAliasForWithProperties() throws Exception { MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(); this.autoConfigurationMetadata = getAutoConfigurationMetadata(X, Y2, Z2); - this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata); - List actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y2, Z2)); + this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER); + List actual = getInPriorityOrder(X, Y2, Z2); assertThat(actual).containsExactly(Z2, Y2, X); } @Test void byAutoConfigureAfterDoubles() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E)); + List actual = getInPriorityOrder(A, B, C, E); assertThat(actual).containsExactly(C, E, B, A); } @Test void byAutoConfigureMixedBeforeAndAfter() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, W, X)); + List actual = getInPriorityOrder(A, B, C, W, X); assertThat(actual).containsExactly(C, W, B, A, X); } @Test void byAutoConfigureMixedBeforeAndAfterWithClassNames() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A2, B, C, W2, X)); + List actual = getInPriorityOrder(A2, B, C, W2, X); assertThat(actual).containsExactly(C, W2, B, A2, X); } @Test void byAutoConfigureMixedBeforeAndAfterWithDifferentInputOrder() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(W, X, A, B, C)); + List actual = getInPriorityOrder(W, X, A, B, C); assertThat(actual).containsExactly(C, W, B, A, X); } @Test void byAutoConfigureAfterWithMissing() { - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B)); + List actual = getInPriorityOrder(A, B); assertThat(actual).containsExactly(B, A); } @Test void byAutoConfigureAfterWithCycle() { - this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(), this.autoConfigurationMetadata); - assertThatIllegalStateException().isThrownBy(() -> this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, D))) + this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(), this.autoConfigurationMetadata, + REPLACEMENT_MAPPER); + assertThatIllegalStateException().isThrownBy(() -> getInPriorityOrder(A, B, C, D)) .withMessageContaining("AutoConfigure cycle detected"); } @@ -184,8 +199,8 @@ void byAutoConfigureAfterWithCycle() { void usesAnnotationPropertiesWhenPossible() throws Exception { MetadataReaderFactory readerFactory = new SkipCycleMetadataReaderFactory(); this.autoConfigurationMetadata = getAutoConfigurationMetadata(A2, B, C, W2, X); - this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata); - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A2, B, C, W2, X)); + this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER); + List actual = getInPriorityOrder(A2, B, C, W2, X); assertThat(actual).containsExactly(C, W2, B, A2, X); } @@ -193,8 +208,8 @@ void usesAnnotationPropertiesWhenPossible() throws Exception { void useAnnotationWithNoDirectLink() throws Exception { MetadataReaderFactory readerFactory = new SkipCycleMetadataReaderFactory(); this.autoConfigurationMetadata = getAutoConfigurationMetadata(A, B, E); - this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata); - List actual = this.sorter.getInPriorityOrder(Arrays.asList(A, E)); + this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER); + List actual = getInPriorityOrder(A, E); assertThat(actual).containsExactly(E, A); } @@ -202,8 +217,8 @@ void useAnnotationWithNoDirectLink() throws Exception { void useAnnotationWithNoDirectLinkAndCycle() throws Exception { MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(); this.autoConfigurationMetadata = getAutoConfigurationMetadata(A, B, D); - this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata); - assertThatIllegalStateException().isThrownBy(() -> this.sorter.getInPriorityOrder(Arrays.asList(D, B))) + this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata, REPLACEMENT_MAPPER); + assertThatIllegalStateException().isThrownBy(() -> getInPriorityOrder(D, B)) .withMessageContaining("AutoConfigure cycle detected"); } @@ -214,10 +229,14 @@ void byBeforeAnnotationThenOrderAnnotation() { String oa2 = OrderAutoConfigureASeedY2.class.getName(); String oa3 = OrderAutoConfigureASeedA3.class.getName(); String oa4 = OrderAutoConfigureAutoConfigureASeedG4.class.getName(); - List actual = this.sorter.getInPriorityOrder(Arrays.asList(oa4, oa3, oa2, oa1, oa)); + List actual = getInPriorityOrder(oa4, oa3, oa2, oa1, oa); assertThat(actual).containsExactly(oa1, oa2, oa3, oa4, oa); } + private List getInPriorityOrder(String... classNames) { + return this.sorter.getInPriorityOrder(Arrays.asList(classNames)); + } + private AutoConfigurationMetadata getAutoConfigurationMetadata(String... classNames) throws Exception { Properties properties = new Properties(); for (String className : classNames) { @@ -303,6 +322,11 @@ static class AutoConfigureA3 { } + @AutoConfigureAfter(AutoConfigureBWithReplaced.class) + public static class AutoConfigureAWithReplaced { + + } + @AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class }) static class AutoConfigureB { @@ -313,10 +337,21 @@ static class AutoConfigureB2 { } + @AutoConfigureAfter({ DeprecatedAutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class }) + public static class AutoConfigureBWithReplaced { + + } + static class AutoConfigureC { } + // @DeprecatedAutoConfiguration(replacement = + // "org.springframework.boot.autoconfigure.AutoConfigurationSorterTests$AutoConfigureC") + public static class DeprecatedAutoConfigureC { + + } + @AutoConfigureAfter(AutoConfigureA.class) static class AutoConfigureD { @@ -350,6 +385,12 @@ static class AutoConfigureY2 { } + // @DeprecatedAutoConfiguration(replacement = + // "org.springframework.boot.autoconfigure.AutoConfigurationSorterTests$AutoConfigureY") + public static class DeprecatedAutoConfigureY { + + } + @AutoConfigureBefore(AutoConfigureY.class) static class AutoConfigureZ { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationsTests.java index 3b7435594a4f..dcfb3da0d7f2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationsTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationsTests.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,8 @@ package org.springframework.boot.autoconfigure; +import java.util.Arrays; + import org.junit.jupiter.api.Test; import org.springframework.boot.context.annotation.Configurations; @@ -36,6 +38,26 @@ void ofShouldCreateOrderedConfigurations() { AutoConfigureA.class); } + @Test + void whenHasReplacementForAutoConfigureAfterShouldCreateOrderedConfigurations() { + Configurations configurations = new AutoConfigurations(this::replaceB, + Arrays.asList(AutoConfigureA.class, AutoConfigureB2.class)); + assertThat(Configurations.getClasses(configurations)).containsExactly(AutoConfigureB2.class, + AutoConfigureA.class); + } + + @Test + void whenHasReplacementForClassShouldReplaceClass() { + Configurations configurations = new AutoConfigurations(this::replaceB, + Arrays.asList(AutoConfigureA.class, AutoConfigureB.class)); + assertThat(Configurations.getClasses(configurations)).containsExactly(AutoConfigureB2.class, + AutoConfigureA.class); + } + + private String replaceB(String className) { + return (!AutoConfigureB.class.getName().equals(className)) ? className : AutoConfigureB2.class.getName(); + } + @AutoConfigureAfter(AutoConfigureB.class) static class AutoConfigureA { @@ -45,4 +67,8 @@ static class AutoConfigureB { } + static class AutoConfigureB2 { + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/SharedMetadataReaderFactoryContextInitializerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/SharedMetadataReaderFactoryContextInitializerTests.java index 6d0e0613caa0..4c3442a494ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/SharedMetadataReaderFactoryContextInitializerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/SharedMetadataReaderFactoryContextInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; @@ -71,9 +70,8 @@ void initializeWhenUsingSupplierDecorates() { ConfigurationClassPostProcessor configurationAnnotationPostProcessor = mock( ConfigurationClassPostProcessor.class); BeanDefinition beanDefinition = BeanDefinitionBuilder - .genericBeanDefinition(ConfigurationClassPostProcessor.class) + .rootBeanDefinition(ConfigurationClassPostProcessor.class, () -> configurationAnnotationPostProcessor) .getBeanDefinition(); - ((AbstractBeanDefinition) beanDefinition).setInstanceSupplier(() -> configurationAnnotationPostProcessor); registry.registerBeanDefinition(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME, beanDefinition); CachingMetadataReaderFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor( diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationSorter.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationSorter.java index 728fbb110d48..c413059ea8d4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationSorter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationSorter.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,6 +19,7 @@ import java.util.Collection; import java.util.List; import java.util.Properties; +import java.util.function.UnaryOperator; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -29,8 +30,9 @@ */ public class TestAutoConfigurationSorter extends AutoConfigurationSorter { - public TestAutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory) { - super(metadataReaderFactory, AutoConfigurationMetadataLoader.loadMetadata(new Properties())); + public TestAutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory, + UnaryOperator replacementMapper) { + super(metadataReaderFactory, AutoConfigurationMetadataLoader.loadMetadata(new Properties()), replacementMapper); } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetailsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetailsTests.java index fb4f65d9cf32..a3b0162b0667 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetailsTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetailsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ class PropertiesRabbitConnectionDetailsTests { @Test void getAddresses() { RabbitProperties properties = new RabbitProperties(); - properties.setAddresses("localhost,localhost:1234,[::1],[::1]:32863"); + properties.setAddresses(List.of("localhost", "localhost:1234", "[::1]", "[::1]:32863")); PropertiesRabbitConnectionDetails propertiesRabbitConnectionDetails = new PropertiesRabbitConnectionDetails( properties); List

addresses = propertiesRabbitConnectionDetails.getAddresses(); 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 c264063e9e4c..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 @@ -17,6 +17,7 @@ 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; @@ -30,15 +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; @@ -58,10 +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; @@ -106,6 +114,7 @@ * @author Andy Wilkinson * @author Phillip Webb * @author Scott Frederick + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) class RabbitAutoConfigurationTests { @@ -320,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); @@ -346,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, @@ -372,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) @@ -458,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); @@ -520,19 +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.force-stop:true") + "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); @@ -540,6 +560,7 @@ 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); }); } @@ -592,23 +613,25 @@ void testDirectRabbitListenerContainerFactoryWithCustomSettings() { .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.force-stop: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); }); } @@ -629,7 +652,7 @@ void testDirectRabbitListenerContainerFactoryWithDefaultForceStop() { 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); @@ -643,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); @@ -666,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) -> { @@ -679,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); @@ -764,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); @@ -774,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) -> { @@ -816,8 +860,8 @@ void enableSslWithInvalidSslBundleFails() { // 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"); @@ -829,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"); @@ -841,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"); @@ -853,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"); @@ -879,10 +923,10 @@ void enableSslWithBundle() { 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()); } @@ -890,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(); @@ -913,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()); } @@ -926,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"); @@ -940,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"); @@ -1098,6 +1142,16 @@ RabbitListenerContainerFactory rabbitListenerContainerFactory() { } + @Configuration(proxyBeanMethods = false) + static class TestConfiguration6 { + + @Bean + MessageConverter messageConverter() { + return new SerializerMessageConverter(); + } + + } + @Configuration(proxyBeanMethods = false) static class MessageConvertersConfiguration { @@ -1372,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 06708ae217d0..333da3c89591 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.autoconfigure.amqp; +import java.util.List; + import com.rabbitmq.client.ConnectionFactory; import org.junit.jupiter.api.Test; @@ -54,7 +56,7 @@ void customHost() { @Test void hostIsDeterminedFromFirstAddress() { - this.properties.setAddresses("rabbit1.example.com:1234,rabbit2.example.com:2345"); + this.properties.setAddresses(List.of("rabbit1.example.com:1234", "rabbit2.example.com:2345")); assertThat(this.properties.determineHost()).isEqualTo("rabbit1.example.com"); } @@ -77,7 +79,7 @@ void customPort() { @Test void determinePortReturnsPortOfFirstAddress() { - this.properties.setAddresses("rabbit1.example.com:1234,rabbit2.example.com:2345"); + this.properties.setAddresses(List.of("rabbit1.example.com:1234", "rabbit2.example.com:2345")); assertThat(this.properties.determinePort()).isEqualTo(1234); } @@ -101,19 +103,19 @@ void determinePortReturnsPortPropertyWhenNoAddresses() { @Test void determinePortReturnsDefaultAmqpPortWhenFirstAddressHasNoExplicitPort() { this.properties.setPort(1234); - this.properties.setAddresses("rabbit1.example.com,rabbit2.example.com:2345"); + this.properties.setAddresses(List.of("rabbit1.example.com", "rabbit2.example.com:2345")); assertThat(this.properties.determinePort()).isEqualTo(5672); } @Test void determinePortUsingAmqpReturnsPortOfFirstAddress() { - this.properties.setAddresses("amqp://root:password@otherhost,amqps://root:password2@otherhost2"); + this.properties.setAddresses(List.of("amqp://root:password@otherhost", "amqps://root:password2@otherhost2")); assertThat(this.properties.determinePort()).isEqualTo(5672); } @Test void determinePortUsingAmqpsReturnsPortOfFirstAddress() { - this.properties.setAddresses("amqps://root:password@otherhost,amqp://root:password2@otherhost2"); + this.properties.setAddresses(List.of("amqps://root:password@otherhost", "amqp://root:password2@otherhost2")); assertThat(this.properties.determinePort()).isEqualTo(5671); } @@ -121,7 +123,7 @@ void determinePortUsingAmqpsReturnsPortOfFirstAddress() { void determinePortReturnsDefaultAmqpsPortWhenFirstAddressHasNoExplicitPortButSslEnabled() { this.properties.getSsl().setEnabled(true); this.properties.setPort(1234); - this.properties.setAddresses("rabbit1.example.com,rabbit2.example.com:2345"); + this.properties.setAddresses(List.of("rabbit1.example.com", "rabbit2.example.com:2345")); assertThat(this.properties.determinePort()).isEqualTo(5671); } @@ -144,7 +146,7 @@ void virtualHostRetainsALeadingSlash() { @Test void determineVirtualHostReturnsVirtualHostOfFirstAddress() { - this.properties.setAddresses("rabbit1.example.com:1234/alpha,rabbit2.example.com:2345/bravo"); + this.properties.setAddresses(List.of("rabbit1.example.com:1234/alpha", "rabbit2.example.com:2345/bravo")); assertThat(this.properties.determineVirtualHost()).isEqualTo("alpha"); } @@ -157,13 +159,13 @@ void determineVirtualHostReturnsPropertyWhenNoAddresses() { @Test void determineVirtualHostReturnsPropertyWhenFirstAddressHasNoVirtualHost() { this.properties.setVirtualHost("alpha"); - this.properties.setAddresses("rabbit1.example.com:1234,rabbit2.example.com:2345/bravo"); + this.properties.setAddresses(List.of("rabbit1.example.com:1234", "rabbit2.example.com:2345/bravo")); assertThat(this.properties.determineVirtualHost()).isEqualTo("alpha"); } @Test void determineVirtualHostIsSlashWhenAddressHasTrailingSlash() { - this.properties.setAddresses("amqp://root:password@otherhost:1111/"); + this.properties.setAddresses(List.of("amqp://root:password@otherhost:1111/")); assertThat(this.properties.determineVirtualHost()).isEqualTo("/"); } @@ -186,7 +188,8 @@ void customUsername() { @Test void determineUsernameReturnsUsernameOfFirstAddress() { - this.properties.setAddresses("user:secret@rabbit1.example.com:1234/alpha,rabbit2.example.com:2345/bravo"); + this.properties + .setAddresses(List.of("user:secret@rabbit1.example.com:1234/alpha", "rabbit2.example.com:2345/bravo")); assertThat(this.properties.determineUsername()).isEqualTo("user"); } @@ -199,7 +202,8 @@ void determineUsernameReturnsPropertyWhenNoAddresses() { @Test void determineUsernameReturnsPropertyWhenFirstAddressHasNoUsername() { this.properties.setUsername("alice"); - this.properties.setAddresses("rabbit1.example.com:1234/alpha,user:secret@rabbit2.example.com:2345/bravo"); + this.properties + .setAddresses(List.of("rabbit1.example.com:1234/alpha", "user:secret@rabbit2.example.com:2345/bravo")); assertThat(this.properties.determineUsername()).isEqualTo("alice"); } @@ -216,7 +220,8 @@ void customPassword() { @Test void determinePasswordReturnsPasswordOfFirstAddress() { - this.properties.setAddresses("user:secret@rabbit1.example.com:1234/alpha,rabbit2.example.com:2345/bravo"); + this.properties + .setAddresses(List.of("user:secret@rabbit1.example.com:1234/alpha", "rabbit2.example.com:2345/bravo")); assertThat(this.properties.determinePassword()).isEqualTo("secret"); } @@ -229,7 +234,8 @@ void determinePasswordReturnsPropertyWhenNoAddresses() { @Test void determinePasswordReturnsPropertyWhenFirstAddressHasNoPassword() { this.properties.setPassword("12345678"); - this.properties.setAddresses("rabbit1.example.com:1234/alpha,user:secret@rabbit2.example.com:2345/bravo"); + this.properties + .setAddresses(List.of("rabbit1.example.com:1234/alpha", "user:secret@rabbit2.example.com:2345/bravo")); assertThat(this.properties.determinePassword()).isEqualTo("12345678"); } @@ -240,79 +246,80 @@ void addressesDefaultsToNull() { @Test void customAddresses() { - this.properties.setAddresses("user:secret@rabbit1.example.com:1234/alpha,rabbit2.example.com"); - assertThat(this.properties.getAddresses()) - .isEqualTo("user:secret@rabbit1.example.com:1234/alpha,rabbit2.example.com"); + this.properties.setAddresses(List.of("user:secret@rabbit1.example.com:1234/alpha", "rabbit2.example.com")); + assertThat(this.properties.getAddresses()).containsExactly("user:secret@rabbit1.example.com:1234/alpha", + "rabbit2.example.com"); } @Test void ipv6Address() { - this.properties.setAddresses("amqp://foo:bar@[aaaa:bbbb:cccc::d]:1234"); + this.properties.setAddresses(List.of("amqp://foo:bar@[aaaa:bbbb:cccc::d]:1234")); assertThat(this.properties.determineHost()).isEqualTo("[aaaa:bbbb:cccc::d]"); assertThat(this.properties.determinePort()).isEqualTo(1234); } @Test void ipv6AddressDefaultPort() { - this.properties.setAddresses("amqp://foo:bar@[aaaa:bbbb:cccc::d]"); + this.properties.setAddresses(List.of("amqp://foo:bar@[aaaa:bbbb:cccc::d]")); assertThat(this.properties.determineHost()).isEqualTo("[aaaa:bbbb:cccc::d]"); assertThat(this.properties.determinePort()).isEqualTo(5672); } @Test void determineAddressesReturnsAddressesWithJustHostAndPort() { - this.properties.setAddresses("user:secret@rabbit1.example.com:1234/alpha,rabbit2.example.com"); - assertThat(this.properties.determineAddresses()).isEqualTo("rabbit1.example.com:1234,rabbit2.example.com:5672"); + this.properties.setAddresses(List.of("user:secret@rabbit1.example.com:1234/alpha", "rabbit2.example.com")); + assertThat(this.properties.determineAddresses()).containsExactly("rabbit1.example.com:1234", + "rabbit2.example.com:5672"); } @Test void determineAddressesUsesDefaultWhenNoAddressesSet() { - assertThat(this.properties.determineAddresses()).isEqualTo("localhost:5672"); + assertThat(this.properties.determineAddresses()).containsExactly("localhost:5672"); } @Test void determineAddressesWithSslUsesDefaultWhenNoAddressesSet() { this.properties.getSsl().setEnabled(true); - assertThat(this.properties.determineAddresses()).isEqualTo("localhost:5671"); + assertThat(this.properties.determineAddresses()).containsExactly("localhost:5671"); } @Test void determineAddressesUsesHostAndPortPropertiesWhenNoAddressesSet() { this.properties.setHost("rabbit.example.com"); this.properties.setPort(1234); - assertThat(this.properties.determineAddresses()).isEqualTo("rabbit.example.com:1234"); + assertThat(this.properties.determineAddresses()).containsExactly("rabbit.example.com:1234"); } @Test void determineAddressesUsesIpv6HostAndPortPropertiesWhenNoAddressesSet() { this.properties.setHost("[::1]"); this.properties.setPort(32863); - assertThat(this.properties.determineAddresses()).isEqualTo("[::1]:32863"); + assertThat(this.properties.determineAddresses()).containsExactly("[::1]:32863"); } @Test void determineSslUsingAmqpsReturnsStateOfFirstAddress() { - this.properties.setAddresses("amqps://root:password@otherhost,amqp://root:password2@otherhost2"); + this.properties.setAddresses(List.of("amqps://root:password@otherhost", "amqp://root:password2@otherhost2")); assertThat(this.properties.getSsl().determineEnabled()).isTrue(); } @Test void sslDetermineEnabledIsTrueWhenAddressHasNoProtocolAndSslIsEnabled() { this.properties.getSsl().setEnabled(true); - this.properties.setAddresses("root:password@otherhost"); + this.properties.setAddresses(List.of("root:password@otherhost")); assertThat(this.properties.getSsl().determineEnabled()).isTrue(); } @Test void sslDetermineEnabledIsFalseWhenAddressHasNoProtocolAndSslIsDisabled() { this.properties.getSsl().setEnabled(false); - this.properties.setAddresses("root:password@otherhost"); + this.properties.setAddresses(List.of("root:password@otherhost")); assertThat(this.properties.getSsl().determineEnabled()).isFalse(); } @Test void determineSslUsingAmqpReturnsStateOfFirstAddress() { - this.properties.setAddresses("amqp://root:password@otherhost,amqps://root:password2@otherhost2"); + this.properties.setAddresses(List.of("amqp://root:password@otherhost", "amqps://root:password2@otherhost2")); assertThat(this.properties.getSsl().determineEnabled()).isFalse(); } @@ -360,7 +367,7 @@ void directContainerUseConsistentDefaultValues() { @Test void determineUsernameWithoutPassword() { - this.properties.setAddresses("user@rabbit1.example.com:1234/alpha"); + this.properties.setAddresses(List.of("user@rabbit1.example.com:1234/alpha")); assertThat(this.properties.determineUsername()).isEqualTo("user"); assertThat(this.properties.determinePassword()).isEqualTo("guest"); } 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 62291403dd08..5ab6f5d4fad6 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 @@ -55,6 +55,7 @@ * @author Gary Russell * @author Andy Wilkinson * @author Eddú Meléndez + * @author Moritz Halbritter */ class RabbitStreamConfigurationTests { @@ -89,6 +90,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)); 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 ae845a1f6bbf..8f10bea4e035 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 @@ -43,7 +43,10 @@ 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; @@ -58,14 +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; @@ -77,6 +78,10 @@ import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.Order; import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -98,6 +103,8 @@ * @author Vedran Pavic * @author Kazuki Shimizu * @author Mahmoud Ben Hassine + * @author Lars Uffmann + * @author Lasse Wulff */ @ExtendWith(OutputCaptureExtension.class) class BatchAutoConfigurationTests { @@ -108,8 +115,7 @@ class BatchAutoConfigurationTests { @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); @@ -347,6 +353,34 @@ 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 testBatchTaskExecutor() { + this.contextRunner + .withUserConfiguration(TestConfiguration.class, BatchTaskExecutorConfiguration.class, + EmbeddedDataSourceConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(SpringBootBatchConfiguration.class).hasBean("batchTaskExecutor"); + TaskExecutor batchTaskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class); + assertThat(batchTaskExecutor).isInstanceOf(AsyncTaskExecutor.class); + assertThat(context.getBean(SpringBootBatchConfiguration.class).getTaskExecutor()) + .isEqualTo(batchTaskExecutor); + assertThat(context.getBean(JobLauncher.class)).hasFieldOrPropertyWithValue("taskExecutor", + batchTaskExecutor); + }); + } + @Test void jobRepositoryBeansDependOnBatchDataSourceInitializer() { this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class) @@ -462,6 +496,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)); @@ -478,22 +533,59 @@ 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() { + @Bean(defaultCandidate = false) + 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(defaultCandidate = false) + PlatformTransactionManager batchTransactionManager() { + return mock(PlatformTransactionManager.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class BatchTaskExecutorConfiguration { + + @Bean + TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + @BatchTaskExecutor + @Bean(defaultCandidate = false) + TaskExecutor batchTaskExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + } + @Configuration(proxyBeanMethods = false) static class EmptyConfiguration { @@ -778,4 +870,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 87a87f90c460..724fda601a68 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 @@ void getSettingsWithPlatformDoesNotTouchDataSource() { } @ParameterizedTest - @EnumSource(value = DatabaseDriver.class, mode = Mode.EXCLUDE, - names = { "FIREBIRD", "INFORMIX", "JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" }) + @EnumSource(value = DatabaseDriver.class, mode = Mode.EXCLUDE, names = { "CLICKHOUSE", "FIREBIRD", "INFORMIX", + "JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" }) void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); BatchProperties properties = new BatchProperties(); 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 ecd0ce8b0471..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 @@ -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; @@ -38,8 +39,6 @@ import org.infinispan.jcache.embedded.JCachingProvider; import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager; 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.BeanCreationException; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -78,8 +77,10 @@ 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; @@ -462,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)) @@ -569,7 +587,6 @@ void hazelcastAsJCacheWithExistingHazelcastInstance() { } @Test - @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_22, disabledReason = "Infinispan 14 does not work on Java 23") void infinispanCacheWithConfig() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) .withPropertyValues("spring.cache.type=infinispan", "spring.cache.infinispan.config=infinispan.xml") @@ -580,7 +597,6 @@ void infinispanCacheWithConfig() { } @Test - @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_22, disabledReason = "Infinispan 14 does not work on Java 23") void infinispanCacheWithCustomizers() { this.contextRunner.withUserConfiguration(DefaultCacheAndCustomizersConfiguration.class) .withPropertyValues("spring.cache.type=infinispan") @@ -588,7 +604,6 @@ void infinispanCacheWithCustomizers() { } @Test - @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_22, disabledReason = "Infinispan 14 does not work on Java 23") void infinispanCacheWithCaches() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) .withPropertyValues("spring.cache.type=infinispan", "spring.cache.cacheNames[0]=foo", @@ -598,7 +613,6 @@ void infinispanCacheWithCaches() { } @Test - @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_22, disabledReason = "Infinispan 14 does not work on Java 23") void infinispanCacheWithCachesAndCustomConfig() { this.contextRunner.withUserConfiguration(InfinispanCustomConfiguration.class) .withPropertyValues("spring.cache.type=infinispan", "spring.cache.cacheNames[0]=foo", @@ -611,7 +625,6 @@ void infinispanCacheWithCachesAndCustomConfig() { } @Test - @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_22, disabledReason = "Infinispan 14 does not work on Java 23") void infinispanAsJCacheWithCaches() { String cachingProviderClassName = JCachingProvider.class.getName(); this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) @@ -622,7 +635,6 @@ void infinispanAsJCacheWithCaches() { } @Test - @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_22, disabledReason = "Infinispan 14 does not work on Java 23") void infinispanAsJCacheWithConfig() { String cachingProviderClassName = JCachingProvider.class.getName(); String configLocation = "infinispan.xml"; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/support/MockCachingProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/support/MockCachingProvider.java index 962eef94e75d..d9e039d888e1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/support/MockCachingProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/support/MockCachingProvider.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. @@ -178,7 +178,7 @@ public boolean isClosed() { } @Override - public T unwrap(Class clazz) { + public T unwrap(Class type) { throw new UnsupportedOperationException(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index 06a49b1e6184..5b2d540ca4a0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ import org.springframework.context.annotation.ImportResource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -242,6 +241,45 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); } + @Test + void conditionalOnBeanTypeIgnoresNotAutowireCandidateBean() { + this.contextRunner + .withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanClassConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void conditionalOnBeanNameMatchesNotAutowireCandidateBean() { + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanNameConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void conditionalOnAnnotatedBeanIgnoresNotAutowireCandidateBean() { + this.contextRunner + .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void conditionalOnBeanTypeIgnoresNotDefaultCandidateBean() { + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanClassConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void conditionalOnBeanNameMatchesNotDefaultCandidateBean() { + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void conditionalOnAnnotatedBeanIgnoresNotDefaultCandidateBean() { + this.contextRunner + .withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + private Consumer exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -273,7 +311,7 @@ String bar() { } @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(annotation = EnableScheduling.class) + @ConditionalOnBean(annotation = TestAnnotation.class) static class OnAnnotationConfiguration { @Bean @@ -317,7 +355,7 @@ String bar() { } @Configuration(proxyBeanMethods = false) - @EnableScheduling + @TestAnnotation static class FooConfiguration { @Bean @@ -327,6 +365,26 @@ String foo() { } + @Configuration(proxyBeanMethods = false) + static class NotAutowireCandidateConfiguration { + + @Bean(autowireCandidate = false) + String foo() { + return "foo"; + } + + } + + @Configuration(proxyBeanMethods = false) + static class NotDefaultCandidateConfiguration { + + @Bean(defaultCandidate = false) + String foo() { + return "foo"; + } + + } + @Configuration(proxyBeanMethods = false) @ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml") static class XmlConfiguration { @@ -530,6 +588,26 @@ TestParameterizedContainer conditionalCustomExampleBean() { } + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotAutowireCandidateConfig { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotDefaultCandidateConfig { + + @Bean(defaultCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + @TestAnnotation static class ExampleBean { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnExpressionTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnExpressionTests.java index 36cb94382c79..b5cb6a9cf8de 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnExpressionTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnExpressionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,12 +65,12 @@ void expressionEvaluationWithNoBeanFactoryDoesNotMatch() { MockEnvironment environment = new MockEnvironment(); ConditionContext conditionContext = mock(ConditionContext.class); given(conditionContext.getEnvironment()).willReturn(environment); - ConditionOutcome outcome = condition.getMatchOutcome(conditionContext, mockMetaData("invalid-spel")); + ConditionOutcome outcome = condition.getMatchOutcome(conditionContext, mockMetadata("invalid-spel")); assertThat(outcome.isMatch()).isFalse(); assertThat(outcome.getMessage()).contains("invalid-spel").contains("no BeanFactory available"); } - private AnnotatedTypeMetadata mockMetaData(String value) { + private AnnotatedTypeMetadata mockMetadata(String value) { AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class); given(metadata.getAnnotationAttributes(ConditionalOnExpression.class.getName())) .willReturn(Collections.singletonMap("value", value)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndiTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndiTests.java index d1635feec1fd..feecc22fb732 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndiTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndiTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,14 +101,14 @@ void jndiLocationBound() { @Test void jndiLocationNotFound() { - ConditionOutcome outcome = this.condition.getMatchOutcome(null, mockMetaData("java:/a")); + ConditionOutcome outcome = this.condition.getMatchOutcome(null, mockMetadata("java:/a")); assertThat(outcome.isMatch()).isFalse(); } @Test void jndiLocationFound() { this.condition.setFoundLocation("java:/b"); - ConditionOutcome outcome = this.condition.getMatchOutcome(null, mockMetaData("java:/a", "java:/b")); + ConditionOutcome outcome = this.condition.getMatchOutcome(null, mockMetadata("java:/a", "java:/b")); assertThat(outcome.isMatch()).isTrue(); } @@ -117,7 +117,7 @@ private void setupJndi() { System.setProperty(Context.INITIAL_CONTEXT_FACTORY, TestableInitialContextFactory.class.getName()); } - private AnnotatedTypeMetadata mockMetaData(String... value) { + private AnnotatedTypeMetadata mockMetadata(String... value) { AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class); Map attributes = new HashMap<>(); attributes.put("value", value); 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 9ca96314e9fa..a51172a0d9dd 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 +46,6 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportResource; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -138,6 +137,14 @@ void testAnnotationOnMissingBeanConditionWithEagerFactoryBean() { }); } + @Test // gh-42484 + void testAnnotationOnMissingBeanConditionOnMethodWhenNoAnnotatedBeans() { + // There are no beans with @TestAnnotation but there is an UnrelatedExampleBean + this.contextRunner + .withUserConfiguration(UnrelatedExampleBeanConfiguration.class, OnAnnotationMethodConfiguration.class) + .run((context) -> assertThat(context).hasBean("conditional")); + } + @Test void testOnMissingBeanConditionOutputShouldNotContainConditionalOnBeanClassInMessage() { this.contextRunner.withUserConfiguration(OnBeanNameConfiguration.class).run((context) -> { @@ -345,6 +352,44 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); } + @Test + void typeBasedMatchingIgnoresBeanThatIsNotAutowireCandidate() { + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanTypeConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void nameBasedMatchingConsidersBeanThatIsNotAutowireCandidate() { + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanNameConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void annotationBasedMatchingIgnoresBeanThatIsNotAutowireCandidateBean() { + this.contextRunner + .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void typeBasedMatchingIgnoresBeanThatIsNotDefaultCandidate() { + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanTypeConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() { + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void annotationBasedMatchingIgnoresBeanThatIsNotDefaultCandidateBean() { + this.contextRunner + .withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + private Consumer exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -375,6 +420,17 @@ String bar() { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(type = "java.lang.String") + static class OnBeanTypeConfiguration { + + @Bean + String bar() { + return "bar"; + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo", value = Date.class) @ConditionalOnBean(name = "foo", value = Date.class) @@ -536,7 +592,7 @@ CustomExampleBean customExampleBean() { } @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(annotation = EnableScheduling.class) + @ConditionalOnMissingBean(annotation = TestAnnotation.class) static class OnAnnotationConfiguration { @Bean @@ -546,6 +602,17 @@ String bar() { } + @Configuration(proxyBeanMethods = false) + static class OnAnnotationMethodConfiguration { + + @Bean + @ConditionalOnMissingBean(annotation = TestAnnotation.class) + UnrelatedExampleBean conditional() { + return new UnrelatedExampleBean("conditional"); + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(annotation = TestAnnotation.class) static class OnAnnotationWithFactoryBeanConfiguration { @@ -558,7 +625,7 @@ String bar() { } @Configuration(proxyBeanMethods = false) - @EnableScheduling + @TestAnnotation static class FooConfiguration { @Bean @@ -568,6 +635,26 @@ String foo() { } + @Configuration(proxyBeanMethods = false) + static class NotAutowireCandidateConfig { + + @Bean(autowireCandidate = false) + String foo() { + return "foo"; + } + + } + + @Configuration(proxyBeanMethods = false) + static class NotDefaultCandidateConfig { + + @Bean(defaultCandidate = false) + String foo() { + return "foo"; + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo") static class HierarchyConsidered { @@ -600,6 +687,16 @@ ExampleBean exampleBean() { } + @Configuration(proxyBeanMethods = false) + static class UnrelatedExampleBeanConfiguration { + + @Bean + UnrelatedExampleBean unrelatedExampleBean() { + return new UnrelatedExampleBean("test"); + } + + } + @Configuration(proxyBeanMethods = false) static class ImpliedOnBeanMethod { @@ -731,6 +828,26 @@ TestParameterizedContainer conditionalCustomExampleBean() { } + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotAutowireCandidateConfig { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotDefaultCandidateConfig { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + @TestAnnotation static class ExampleBean { @@ -763,6 +880,21 @@ static class OtherExampleBean extends ExampleBean { } + static class UnrelatedExampleBean { + + private final String value; + + UnrelatedExampleBean(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + } + @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java index 292c119f11cd..6cb9bad7c9b2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Fallback; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; @@ -114,6 +115,17 @@ void singleCandidateMultipleCandidatesOnePrimary() { }); } + @Test + void singleCandidateTwoCandidatesOneNormalOneFallback() { + this.contextRunner + .withUserConfiguration(AlphaFallbackConfiguration.class, BravoConfiguration.class, + OnBeanSingleCandidateConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("consumer"); + assertThat(context.getBean("consumer")).isEqualTo("bravo"); + }); + } + @Test void singleCandidateMultipleCandidatesMultiplePrimary() { this.contextRunner @@ -122,6 +134,14 @@ void singleCandidateMultipleCandidatesMultiplePrimary() { .run((context) -> assertThat(context).doesNotHaveBean("consumer")); } + @Test + void singleCandidateMultipleCandidatesAllFallback() { + this.contextRunner + .withUserConfiguration(AlphaFallbackConfiguration.class, BravoFallbackConfiguration.class, + OnBeanSingleCandidateConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("consumer")); + } + @Test void invalidAnnotationTwoTypes() { this.contextRunner.withUserConfiguration(OnBeanSingleCandidateTwoTypesConfiguration.class).run((context) -> { @@ -153,6 +173,28 @@ void singleCandidateMultipleCandidatesInContextHierarchy() { })); } + @Test + void singleCandidateMultipleCandidatesOneAutowireCandidate() { + this.contextRunner + .withUserConfiguration(AlphaConfiguration.class, BravoNonAutowireConfiguration.class, + OnBeanSingleCandidateConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("consumer"); + assertThat(context.getBean("consumer")).isEqualTo("alpha"); + }); + } + + @Test + void singleCandidateMultipleCandidatesOneDefaultCandidate() { + this.contextRunner + .withUserConfiguration(AlphaConfiguration.class, BravoNonDefaultConfiguration.class, + OnBeanSingleCandidateConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("consumer"); + assertThat(context.getBean("consumer")).isEqualTo("alpha"); + }); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(String.class) static class OnBeanSingleCandidateConfiguration { @@ -208,6 +250,17 @@ String alpha() { } + @Configuration(proxyBeanMethods = false) + static class AlphaFallbackConfiguration { + + @Bean + @Fallback + String alpha() { + return "alpha"; + } + + } + @Configuration(proxyBeanMethods = false) static class AlphaScopedProxyConfiguration { @@ -240,4 +293,35 @@ String bravo() { } + @Configuration(proxyBeanMethods = false) + static class BravoFallbackConfiguration { + + @Bean + @Fallback + String bravo() { + return "bravo"; + } + + } + + @Configuration(proxyBeanMethods = false) + static class BravoNonAutowireConfiguration { + + @Bean(autowireCandidate = false) + String bravo() { + return "bravo"; + } + + } + + @Configuration(proxyBeanMethods = false) + static class BravoNonDefaultConfiguration { + + @Bean(defaultCandidate = false) + String bravo() { + return "bravo"; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/container/ContainerImageMetadataTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/container/ContainerImageMetadataTests.java new file mode 100644 index 000000000000..28822d8dac73 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/container/ContainerImageMetadataTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.container; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.AttributeAccessor; +import org.springframework.core.AttributeAccessorSupport; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ContainerImageMetadata}. + * + * @author Phillip Webb + */ +class ContainerImageMetadataTests { + + private ContainerImageMetadata metadata = new ContainerImageMetadata("test"); + + private AttributeAccessor attributes = new AttributeAccessorSupport() { + + }; + + @Test + void addToWhenAttributesIsNullDoesNothing() { + this.metadata.addTo(null); + } + + @Test + void addToAddsMetadata() { + this.metadata.addTo(this.attributes); + assertThat(this.attributes.getAttribute(ContainerImageMetadata.NAME)).isSameAs(this.metadata); + } + + @Test + void isPresentWhenPresentReturnsTrue() { + this.metadata.addTo(this.attributes); + assertThat(ContainerImageMetadata.isPresent(this.attributes)).isTrue(); + } + + @Test + void isPresentWhenNotPresentReturnsFalse() { + assertThat(ContainerImageMetadata.isPresent(this.attributes)).isFalse(); + } + + @Test + void isPresentWhenNullAttributesReturnsFalse() { + assertThat(ContainerImageMetadata.isPresent(null)).isFalse(); + } + + @Test + void getFromWhenPresentReturnsMetadata() { + this.metadata.addTo(this.attributes); + assertThat(ContainerImageMetadata.getFrom(this.attributes)).isSameAs(this.metadata); + } + + @Test + void getFromWhenNotPresentReturnsNull() { + assertThat(ContainerImageMetadata.getFrom(this.attributes)).isNull(); + } + + @Test + void getFromWhenNullAttributesReturnsNull() { + assertThat(ContainerImageMetadata.getFrom(null)).isNull(); + } + +} 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 e30983164136..fbe9204e4f94 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 Stephane Nicoll * @author Kedar Joshi * @author Marc Becker + * @author Misagh Moayyed + * @author Phillip Webb */ class MessageSourceAutoConfigurationTests { @@ -58,7 +60,7 @@ void testDefaultMessageSource() { @Test void propertiesBundleWithSlashIsDetected() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/messages").run((context) -> { + this.contextRunner.withPropertyValues("spring.messages.basename=test/messages").run((context) -> { assertThat(context).hasSingleBean(MessageSource.class); assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar"); }); @@ -66,7 +68,7 @@ void propertiesBundleWithSlashIsDetected() { @Test void propertiesBundleWithDotIsDetected() { - this.contextRunner.withPropertyValues("spring.messages.basename:test.messages").run((context) -> { + this.contextRunner.withPropertyValues("spring.messages.basename=test.messages").run((context) -> { assertThat(context).hasSingleBean(MessageSource.class); assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar"); }); @@ -74,7 +76,7 @@ void propertiesBundleWithDotIsDetected() { @Test void testEncodingWorks() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/swedish") + this.contextRunner.withPropertyValues("spring.messages.basename=test/swedish") .run((context) -> assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)) .isEqualTo("Some text with some swedish öäå!")); } @@ -82,14 +84,14 @@ void testEncodingWorks() { @Test void testCacheDurationNoUnit() { this.contextRunner - .withPropertyValues("spring.messages.basename:test/messages", "spring.messages.cache-duration=10") + .withPropertyValues("spring.messages.basename=test/messages", "spring.messages.cache-duration=10") .run(assertCache(10 * 1000)); } @Test void testCacheDurationWithUnit() { this.contextRunner - .withPropertyValues("spring.messages.basename:test/messages", "spring.messages.cache-duration=1m") + .withPropertyValues("spring.messages.basename=test/messages", "spring.messages.cache-duration=1m") .run(assertCache(60 * 1000)); } @@ -102,7 +104,7 @@ private ContextConsumer assertCache(long expected) @Test void testMultipleMessageSourceCreated() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/messages,test/messages2") + this.contextRunner.withPropertyValues("spring.messages.basename=test/messages,test/messages2") .run((context) -> { assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar"); assertThat(context.getMessage("foo-foo", null, "Foo-Foo message", Locale.UK)).isEqualTo("bar-bar"); @@ -116,9 +118,27 @@ void testMessageSourceFromPropertySourceAnnotation() { .run((context) -> assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar")); } + @Test + void testCommonMessages() { + this.contextRunner + .withPropertyValues("spring.messages.basename=test/messages", + "spring.messages.common-messages=classpath:test/common-messages.properties") + .run((context) -> assertThat(context.getMessage("hello", null, "Hello!", Locale.UK)).isEqualTo("world")); + } + + @Test + void testCommonMessagesWhenNotFound() { + this.contextRunner + .withPropertyValues("spring.messages.basename=test/messages", + "spring.messages.common-messages=classpath:test/common-messages-missing.properties") + .run((context) -> assertThat(context).getFailure() + .hasMessageContaining( + "Failed to load common messages from 'class path resource [test/common-messages-missing.properties]'")); + } + @Test void testFallbackDefault() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/messages") + this.contextRunner.withPropertyValues("spring.messages.basename=test/messages") .run((context) -> assertThat(context.getBean(MessageSource.class)) .hasFieldOrPropertyWithValue("fallbackToSystemLocale", true)); } @@ -126,7 +146,7 @@ void testFallbackDefault() { @Test void testFallbackTurnOff() { this.contextRunner - .withPropertyValues("spring.messages.basename:test/messages", + .withPropertyValues("spring.messages.basename=test/messages", "spring.messages.fallback-to-system-locale:false") .run((context) -> assertThat(context.getBean(MessageSource.class)) .hasFieldOrPropertyWithValue("fallbackToSystemLocale", false)); @@ -134,7 +154,7 @@ void testFallbackTurnOff() { @Test void testFormatMessageDefault() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/messages") + this.contextRunner.withPropertyValues("spring.messages.basename=test/messages") .run((context) -> assertThat(context.getBean(MessageSource.class)) .hasFieldOrPropertyWithValue("alwaysUseMessageFormat", false)); } @@ -142,7 +162,7 @@ void testFormatMessageDefault() { @Test void testFormatMessageOn() { this.contextRunner - .withPropertyValues("spring.messages.basename:test/messages", + .withPropertyValues("spring.messages.basename=test/messages", "spring.messages.always-use-message-format:true") .run((context) -> assertThat(context.getBean(MessageSource.class)) .hasFieldOrPropertyWithValue("alwaysUseMessageFormat", true)); @@ -150,7 +170,7 @@ void testFormatMessageOn() { @Test void testUseCodeAsDefaultMessageDefault() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/messages") + this.contextRunner.withPropertyValues("spring.messages.basename=test/messages") .run((context) -> assertThat(context.getBean(MessageSource.class)) .hasFieldOrPropertyWithValue("useCodeAsDefaultMessage", false)); } @@ -158,8 +178,8 @@ void testUseCodeAsDefaultMessageDefault() { @Test void testUseCodeAsDefaultMessageOn() { this.contextRunner - .withPropertyValues("spring.messages.basename:test/messages", - "spring.messages.use-code-as-default-message:true") + .withPropertyValues("spring.messages.basename=test/messages", + "spring.messages.use-code-as-default-message=true") .run((context) -> assertThat(context.getBean(MessageSource.class)) .hasFieldOrPropertyWithValue("useCodeAsDefaultMessage", true)); } @@ -173,13 +193,13 @@ void existingMessageSourceIsPreferred() { @Test void existingMessageSourceInParentIsIgnored() { this.contextRunner.run((parent) -> this.contextRunner.withParent(parent) - .withPropertyValues("spring.messages.basename:test/messages") + .withPropertyValues("spring.messages.basename=test/messages") .run((context) -> assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar"))); } @Test void messageSourceWithNonStandardBeanNameIsIgnored() { - this.contextRunner.withPropertyValues("spring.messages.basename:test/messages") + this.contextRunner.withPropertyValues("spring.messages.basename=test/messages") .withUserConfiguration(CustomBeanNameMessageSourceConfiguration.class) .run((context) -> assertThat(context.getMessage("foo", null, Locale.US)).isEqualTo("bar")); } 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..73b5f3ded97c 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. @@ -21,7 +21,10 @@ import java.util.Set; import java.util.function.Consumer; +import com.couchbase.client.core.env.Authenticator; +import com.couchbase.client.core.env.CertificateAuthenticator; import com.couchbase.client.core.env.IoConfig; +import com.couchbase.client.core.env.PasswordAuthenticator; import com.couchbase.client.core.env.SecurityConfig; import com.couchbase.client.core.env.TimeoutConfig; import com.couchbase.client.java.Cluster; @@ -54,6 +57,7 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick */ class CouchbaseAutoConfigurationTests { @@ -63,6 +67,7 @@ class CouchbaseAutoConfigurationTests { @Test void connectionStringIsRequired() { this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ClusterEnvironment.class) + .doesNotHaveBean(Authenticator.class) .doesNotHaveBean(Cluster.class)); } @@ -79,11 +84,12 @@ void shouldUseCustomConnectionDetailsWhenDefined() { .run((context) -> { assertThat(context).hasSingleBean(ClusterEnvironment.class) .hasSingleBean(Cluster.class) + .hasSingleBean(PasswordAuthenticator.class) .hasSingleBean(CouchbaseConnectionDetails.class) .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"); }); @@ -94,22 +100,27 @@ void connectionStringCreateEnvironmentAndCluster() { this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class) .withPropertyValues("spring.couchbase.connection-string=localhost") .run((context) -> { - assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class); + assertThat(context).hasSingleBean(ClusterEnvironment.class) + .hasSingleBean(Authenticator.class) + .hasSingleBean(Cluster.class); + assertThat(context).doesNotHaveBean("couchbaseAuthenticator"); assertThat(context.getBean(Cluster.class)) .isSameAs(context.getBean(CouchbaseTestConfiguration.class).couchbaseCluster()); }); } @Test - void connectionDetailsShouldOverrideProperties() { + void connectionDetailsOverridesProperties() { this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails) .withPropertyValues("spring.couchbase.connection-string=localhost", "spring.couchbase.username=a-user", "spring.couchbase.password=a-password") .run((context) -> { - assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class); + assertThat(context).hasSingleBean(ClusterEnvironment.class) + .hasSingleBean(PasswordAuthenticator.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 +200,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 +224,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) -> { @@ -262,6 +254,41 @@ void customizeEnvWithCustomCouchbaseConfiguration() { }); } + @Test + void passwordAuthenticationWithUsernameAndPassword() { + this.contextRunner + .withPropertyValues("spring.couchbase.connection-string=localhost", "spring.couchbase.username=user", + "spring.couchbase.password=secret") + .run((context) -> assertThat(context).hasSingleBean(PasswordAuthenticator.class)); + } + + @Test + void certificateAuthenticationWithPemPrivateKeyAndCertificate() { + this.contextRunner.withPropertyValues("spring.couchbase.connection-string=localhost", + "spring.couchbase.env.ssl.enabled=true", + "spring.couchbase.authentication.pem.private-key=classpath:org/springframework/boot/autoconfigure/ssl/key2.pem", + "spring.couchbase.authentication.pem.certificates=classpath:org/springframework/boot/autoconfigure/ssl/key2.crt") + .run((context) -> assertThat(context).hasSingleBean(CertificateAuthenticator.class)); + } + + @Test + void certificateAuthenticationWithJavaKeyStore() { + this.contextRunner.withPropertyValues("spring.couchbase.connection-string=localhost", + "spring.couchbase.env.ssl.enabled=true", + "spring.couchbase.authentication.jks.location=classpath:org/springframework/boot/autoconfigure/ssl/keystore.jks", + "spring.couchbase.authentication.jks.password=secret") + .run((context) -> assertThat(context).hasSingleBean(CertificateAuthenticator.class)); + } + + @Test + void failsWithMissingAuthentication() { + this.contextRunner.withPropertyValues("spring.couchbase.connection-string=localhost").run((context) -> { + assertThat(context).hasFailed(); + assertThat(context).getFailure() + .hasMessageContaining("Couchbase authentication requires username and password, or certificates"); + }); + } + private CouchbaseConnectionDetails couchbaseConnectionDetails() { return new CouchbaseConnectionDetails() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java index b2b1cf8c8523..c9f97d6e7e63 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.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,6 +16,7 @@ package org.springframework.boot.autoconfigure.couchbase; +import com.couchbase.client.core.env.Authenticator; import com.couchbase.client.java.Cluster; import org.springframework.context.annotation.Bean; @@ -27,15 +28,23 @@ * Test configuration for couchbase that mocks access. * * @author Stephane Nicoll + * @author Scott Frederick */ @Configuration(proxyBeanMethods = false) class CouchbaseTestConfiguration { private final Cluster cluster = mock(Cluster.class); + private final Authenticator authenticator = mock(Authenticator.class); + @Bean Cluster couchbaseCluster() { return this.cluster; } + @Bean + Authenticator couchbaseAuth() { + return this.authenticator; + } + } 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 064d5d34c5aa..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, 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 8e6dc52f51fd..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 @@ -127,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()); }); } @@ -638,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/data/web/SpringDataWebAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfigurationTests.java index 67cf7109ca3c..d3a1e92406ff 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.data.domain.PageRequest; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.data.web.SortHandlerMethodArgumentResolver; +import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode; +import org.springframework.data.web.config.SpringDataWebSettings; import static org.assertj.core.api.Assertions.assertThat; @@ -33,6 +35,7 @@ * @author Andy Wilkinson * @author Vedran Pavic * @author Stephane Nicoll + * @author Yanming Zhou */ class SpringDataWebAutoConfigurationTests { @@ -53,13 +56,16 @@ void autoConfigurationBacksOffInNonWebApplicationContexts() { @Test void customizePageable() { - this.contextRunner.withPropertyValues("spring.data.web.pageable.page-parameter=p", - "spring.data.web.pageable.size-parameter=s", "spring.data.web.pageable.default-page-size=10", - "spring.data.web.pageable.prefix=abc", "spring.data.web.pageable.qualifier-delimiter=__", - "spring.data.web.pageable.max-page-size=100", "spring.data.web.pageable.one-indexed-parameters=true") + this.contextRunner + .withPropertyValues("spring.data.web.pageable.page-parameter=p", + "spring.data.web.pageable.size-parameter=s", "spring.data.web.pageable.default-page-size=10", + "spring.data.web.pageable.prefix=abc", "spring.data.web.pageable.qualifier-delimiter=__", + "spring.data.web.pageable.max-page-size=100", "spring.data.web.pageable.serialization-mode=VIA_DTO", + "spring.data.web.pageable.one-indexed-parameters=true") .run((context) -> { PageableHandlerMethodArgumentResolver argumentResolver = context .getBean(PageableHandlerMethodArgumentResolver.class); + SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class); assertThat(argumentResolver).hasFieldOrPropertyWithValue("pageParameterName", "p"); assertThat(argumentResolver).hasFieldOrPropertyWithValue("sizeParameterName", "s"); assertThat(argumentResolver).hasFieldOrPropertyWithValue("oneIndexedParameters", true); @@ -67,6 +73,7 @@ void customizePageable() { assertThat(argumentResolver).hasFieldOrPropertyWithValue("qualifierDelimiter", "__"); assertThat(argumentResolver).hasFieldOrPropertyWithValue("fallbackPageable", PageRequest.of(0, 10)); assertThat(argumentResolver).hasFieldOrPropertyWithValue("maxPageSize", 100); + assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(PageSerializationMode.VIA_DTO); }); } @@ -76,6 +83,7 @@ void defaultPageable() { SpringDataWebProperties.Pageable properties = new SpringDataWebProperties().getPageable(); PageableHandlerMethodArgumentResolver argumentResolver = context .getBean(PageableHandlerMethodArgumentResolver.class); + SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class); assertThat(argumentResolver).hasFieldOrPropertyWithValue("pageParameterName", properties.getPageParameter()); assertThat(argumentResolver).hasFieldOrPropertyWithValue("sizeParameterName", @@ -88,6 +96,7 @@ void defaultPageable() { assertThat(argumentResolver).hasFieldOrPropertyWithValue("fallbackPageable", PageRequest.of(0, properties.getDefaultPageSize())); assertThat(argumentResolver).hasFieldOrPropertyWithValue("maxPageSize", properties.getMaxPageSize()); + assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(properties.getSerializationMode()); }); } @@ -100,4 +109,24 @@ void customizeSort() { }); } + @Test + void customizePageSerializationModeViaConfigProps() { + this.contextRunner.withPropertyValues("spring.data.web.pageable.serialization-mode=VIA_DTO").run((context) -> { + SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class); + assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(PageSerializationMode.VIA_DTO); + }); + } + + @Test + void customizePageSerializationModeViaCustomBean() { + this.contextRunner + .withBean("customSpringDataWebSettings", SpringDataWebSettings.class, + () -> new SpringDataWebSettings(PageSerializationMode.VIA_DTO)) + .run((context) -> { + assertThat(context).doesNotHaveBean("springDataWebSettings"); + SpringDataWebSettings springDataWebSettings = context.getBean(SpringDataWebSettings.class); + assertThat(springDataWebSettings.pageSerializationMode()).isEqualTo(PageSerializationMode.VIA_DTO); + }); + } + } 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/Flyway10xNativeImageResourceProviderCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xNativeImageResourceProviderCustomizerTests.java deleted file mode 100644 index 29d60254b65e..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xNativeImageResourceProviderCustomizerTests.java +++ /dev/null @@ -1,52 +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.autoconfigure.flyway; - -import java.util.Collection; - -import org.flywaydb.core.api.ResourceProvider; -import org.flywaydb.core.api.configuration.FluentConfiguration; -import org.flywaydb.core.api.resource.LoadableResource; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.testsupport.classpath.ClassPathOverrides; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link NativeImageResourceProviderCustomizer} with Flyway 10. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Maziz Esa - */ -@ClassPathOverrides("org.flywaydb:flyway-core:10.12.0") -class Flyway10xNativeImageResourceProviderCustomizerTests { - - private final NativeImageResourceProviderCustomizer customizer = new NativeImageResourceProviderCustomizer(); - - @Test - void nativeImageResourceProviderShouldFindMigrations() { - FluentConfiguration configuration = new FluentConfiguration(); - this.customizer.customize(configuration); - ResourceProvider resourceProvider = configuration.getResourceProvider(); - Collection migrations = resourceProvider.getResources("V", new String[] { ".sql" }); - LoadableResource migration = resourceProvider.getResource("V1__init.sql"); - assertThat(migrations).containsExactly(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 97fd9ad3e6d9..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway90AutoConfigurationTests.java +++ /dev/null @@ -1,56 +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.ClassPathExclusions; -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 - */ -@ClassPathExclusions("flyway-*.jar") -@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/Flyway91AutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway91AutoConfigurationTests.java deleted file mode 100644 index ee7cb7ff751a..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway91AutoConfigurationTests.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 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 9.19. - * - * @author Andy Wilkinson - */ -@ClassPathExclusions("flyway-*.jar") -@ClassPathOverrides("org.flywaydb:flyway-core:9.19.4") -class Flyway91AutoConfigurationTests { - - 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 c462bc02716a..49178e27473c 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. @@ -33,9 +33,9 @@ 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.database.postgresql.PostgreSQLConfigurationExtension; -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; @@ -66,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; @@ -318,7 +317,8 @@ void schemaManagementProviderDetectsDataSource() { .run((context) -> { FlywaySchemaManagementProvider schemaManagementProvider = context .getBean(FlywaySchemaManagementProvider.class); - assertThat(schemaManagementProvider.getSchemaManagement(context.getBean(DataSource.class))) + assertThat(schemaManagementProvider + .getSchemaManagement(context.getBean("normalDataSource", DataSource.class))) .isEqualTo(SchemaManagement.UNMANAGED); assertThat(schemaManagementProvider .getSchemaManagement(context.getBean("flywayDataSource", DataSource.class))) @@ -584,7 +584,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 @@ -601,14 +604,6 @@ void errorOverridesIsCorrectlyMapped() { .run(validateFlywayTeamsPropertyOnly("errorOverrides")); } - @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")); - } - @Test void oracleExtensionIsNotLoadedByDefault() { FluentConfiguration configuration = mock(FluentConfiguration.class); @@ -714,14 +709,10 @@ void oracleKerberosCacheFileIsCorrectlyMappedWithDeprecatedProperty() { void streamIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.stream=true") - .run(validateFlywayTeamsPropertyOnly("stream")); - } - - @Test - void undoSqlMigrationPrefix() { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.undo-sql-migration-prefix=undo") - .run(validateFlywayTeamsPropertyOnly("undoSqlMigrationPrefix")); + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getModernConfig().getFlyway().getStream()).isTrue(); + }); } @Test @@ -756,18 +747,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 @@ -781,7 +771,10 @@ void kerberosConfigFileIsCorrectlyMapped() { void outputQueryResultsIsCorrectlyMapped() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.flyway.output-query-results=false") - .run(validateFlywayTeamsPropertyOnly("outputQueryResults")); + .run((context) -> { + Flyway flyway = context.getBean(Flyway.class); + assertThat(flyway.getConfiguration().getModernConfig().getFlyway().getOutputQueryResults()).isFalse(); + }); } @Test @@ -840,7 +833,11 @@ void sqlServerKerberosLoginFileIsCorrectlyMappedWithDeprecatedProperty() { 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 @@ -923,7 +920,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)); }; } @@ -932,13 +929,12 @@ private ContextConsumer validateFlywayTeamsPropert static class FlywayDataSourceConfiguration { @Bean - @Primary DataSource normalDataSource() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:normal").username("sa").build(); } @FlywayDataSource - @Bean + @Bean(defaultCandidate = false) DataSource flywayDataSource() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:flywaytest").username("sa").build(); } @@ -959,7 +955,7 @@ DataSource secondDataSource() { } @FlywayDataSource - @Bean + @Bean(defaultCandidate = false) DataSource flywayDataSource() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:flywaytest").username("sa").build(); } 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 0562b6c48e9a..fbf15285972b 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. @@ -46,6 +46,7 @@ class FlywayPropertiesTests { @Test + @SuppressWarnings("removal") void defaultValuesAreConsistent() { FlywayProperties properties = new FlywayProperties(); Configuration configuration = new FluentConfiguration(); @@ -93,6 +94,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 @@ -114,6 +116,8 @@ void expectedPropertiesAreManaged() { "oracleWalletLocation", "sqlServerKerberosLoginFile"); // Properties that are managed by specific extensions ignoreProperties(properties, "oracle", "postgresql", "sqlserver"); + // Properties that are only used on the command line + ignoreProperties(configuration, "jarDirs"); // https://github.com/flyway/flyway/issues/3732 ignoreProperties(configuration, "environment"); // High level object we can't set with properties @@ -121,7 +125,9 @@ void expectedPropertiesAreManaged() { "javaMigrationClassProvider", "pluginRegister", "resourceProvider", "resolvers"); // Properties we don't want to expose ignoreProperties(configuration, "resolversAsClassNames", "callbacksAsClassNames", "driver", "modernConfig", - "currentResolvedEnvironment", "reportFilename", "reportEnabled", "workingDirectory"); + "currentResolvedEnvironment", "reportFilename", "reportEnabled", "workingDirectory", + "cachedDataSources", "cachedResolvedEnvironments", "currentEnvironmentName", "allEnvironments", + "environmentProvisionMode", "provisionMode"); // Handled by the conversion service ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings", "targetAsString"); @@ -136,6 +142,9 @@ void expectedPropertiesAreManaged() { 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()); @@ -150,6 +159,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/freemarker/FreeMarkerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfigurationTests.java index 77658ef5fb0e..156abc518671 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,14 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.testsupport.BuildOutput; +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; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; /** * Tests for {@link FreeMarkerAutoConfiguration}. @@ -80,6 +86,24 @@ void nonExistentLocationAndEmptyLocation(CapturedOutput output) { .run((context) -> assertThat(output).doesNotContain("Cannot find template location")); } + @Test + void variableCustomizerShouldBeApplied() { + FreeMarkerVariablesCustomizer customizer = mock(FreeMarkerVariablesCustomizer.class); + this.contextRunner.withBean(FreeMarkerVariablesCustomizer.class, () -> customizer) + .run((context) -> then(customizer).should().customizeFreeMarkerVariables(any())); + } + + @Test + @SuppressWarnings("unchecked") + void variableCustomizersShouldBeAppliedInOrder() { + this.contextRunner.withUserConfiguration(VariablesCustomizersConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(freemarker.template.Configuration.class); + freemarker.template.Configuration configuration = context.getBean(freemarker.template.Configuration.class); + assertThat(configuration.getSharedVariableNames()).contains("order", "one", "two"); + assertThat(configuration.getSharedVariable("order")).hasToString("5"); + }); + } + public static class DataModel { public String getGreeting() { @@ -88,4 +112,27 @@ public String getGreeting() { } + @Configuration(proxyBeanMethods = false) + static class VariablesCustomizersConfiguration { + + @Bean + @Order(5) + FreeMarkerVariablesCustomizer variablesCustomizer() { + return (variables) -> { + variables.put("order", 5); + variables.put("one", "one"); + }; + } + + @Bean + @Order(2) + FreeMarkerVariablesCustomizer anotherVariablesCustomizer() { + return (variables) -> { + variables.put("order", 2); + variables.put("two", "two"); + }; + } + + } + } 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 4943bd3ef970..f7e7543edeec 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 @@ -44,6 +44,7 @@ import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; import org.springframework.graphql.ExecutionGraphQlService; +import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; import org.springframework.graphql.data.pagination.EncodingCursorStrategy; import org.springframework.graphql.execution.BatchLoaderRegistry; @@ -125,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); }); }); } @@ -231,6 +234,15 @@ void whenCustomExecutorIsDefinedThenAnnotatedControllerConfigurerDoesNotUseIt() }); } + @Test + void whenAHandlerMethodArgumentResolverIsDefinedThenAnnotatedControllerConfigurerShouldUseIt() { + this.contextRunner.withUserConfiguration(CustomHandlerMethodArgumentResolverConfiguration.class) + .run((context) -> assertThat(context.getBean(AnnotatedControllerConfigurer.class)) + .extracting("customArgumentResolvers") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .hasSize(1)); + } + @Configuration(proxyBeanMethods = false) static class CustomGraphQlBuilderConfiguration { @@ -326,4 +338,13 @@ Executor customExecutor() { } + static class CustomHandlerMethodArgumentResolverConfiguration { + + @Bean + HandlerMethodArgumentResolver customHandlerMethodArgumentResolver() { + return mock(HandlerMethodArgumentResolver.class); + } + + } + } 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..51978cc79fcd 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; @@ -94,7 +96,46 @@ void simpleQueryShouldWork() { } @Test - void httpGetQueryShouldBeSupported() { + 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 unsupportedContentTypeShouldBeRejected() { + testWithWebClient((client) -> { + String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; + client.post() + .uri("/graphql") + .contentType(MediaType.TEXT_PLAIN) + .bodyValue("{ \"query\": \"" + query + "\"}") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + .expectHeader() + .valueEquals("Accept", "application/json"); + }); + } + + @Test + void httpGetQueryShouldBeRejected() { testWithWebClient((client) -> { String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; client.get() @@ -196,6 +237,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 +288,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/GraphQlWebMvcSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/security/GraphQlWebMvcSecurityAutoConfigurationTests.java index 6a0abd80b985..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; @@ -46,17 +47,13 @@ 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}. @@ -81,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 { 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 cd082e39ac6b..9f6a455ab07a 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,9 +43,9 @@ 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; @@ -51,13 +53,7 @@ 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}. @@ -84,77 +80,113 @@ 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 httpGetQueryShouldBeSupported() { - testWith((mockMvc) -> { + 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 unsupportedContentTypeShouldBeRejected() { + withMockMvc((mvc) -> { + String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }"; + assertThat(mvc.post() + .uri("/graphql") + .content("{\"query\": \"" + query + "\"}") + .contentType(MediaType.TEXT_PLAIN)).hasStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + .headers() + .hasValue("Accept", "application/json"); + }); + } + + @Test + void httpGetQueryShouldBeRejected() { + 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"))); }); } @@ -168,6 +200,20 @@ void shouldConfigureWebSocketBeans() { }); } + @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) -> { @@ -186,29 +232,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 179280d8d180..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); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java index 2017fe4332ff..56bac0150678 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.nio.file.Files; +import java.util.Set; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; @@ -150,6 +151,21 @@ void clientConfigTakesPrecedence() { .isInstanceOf(HazelcastClientProxy.class)); } + @Test + void connectionDetailsTakesPrecedenceOverConfigFile() { + this.contextRunner.withUserConfiguration(HazelcastConnectionDetailsConfig.class) + .withPropertyValues("spring.hazelcast.config=this-is-ignored.xml") + .run(assertSpecificHazelcastClient("connection-details")); + } + + @Test + void connectionDetailsTakesPrecedenceOverUserDefinedClientConfig() { + this.contextRunner + .withUserConfiguration(HazelcastConnectionDetailsConfig.class, HazelcastServerAndClientConfig.class) + .withPropertyValues("spring.hazelcast.config=this-is-ignored.xml") + .run(assertSpecificHazelcastClient("connection-details")); + } + @Test void clientConfigWithInstanceNameCreatesClientIfNecessary() throws MalformedURLException { assertThat(HazelcastClient.getHazelcastClientByName("spring-boot")).isNull(); @@ -202,6 +218,20 @@ private File prepareConfiguration(String input) { } } + @Configuration(proxyBeanMethods = false) + static class HazelcastConnectionDetailsConfig { + + @Bean + HazelcastConnectionDetails hazelcastConnectionDetails() { + ClientConfig config = new ClientConfig(); + config.setLabels(Set.of("connection-details")); + config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000); + config.getNetworkConfig().getAddresses().add(endpointAddress); + return () -> config; + } + + } + @Configuration(proxyBeanMethods = false) static class HazelcastServerAndClientConfig { 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/http/client/HttpClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java new file mode 100644 index 000000000000..85a23fae4a02 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.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.autoconfigure.http.client; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects; +import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HttpClientAutoConfiguration}. + * + * @author Phillip Webb + */ +class HttpClientAutoConfigurationTests { + + private static final AutoConfigurations autoConfigurations = AutoConfigurations + .of(HttpClientAutoConfiguration.class, SslAutoConfiguration.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(autoConfigurations); + + @Test + void configuresDetectedClientHttpRequestFactoryBuilder() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ClientHttpRequestFactoryBuilder.class)); + } + + @Test + void configuresDefinedClientHttpRequestFactoryBuilder() { + this.contextRunner.withPropertyValues("spring.http.client.factory=simple") + .run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class)) + .isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class)); + } + + @Test + void configuresClientHttpRequestFactorySettings() { + this.contextRunner.withPropertyValues(sslPropertyValues().toArray(String[]::new)) + .withPropertyValues("spring.http.client.redirects=dont-follow", "spring.http.client.connect-timeout=10s", + "spring.http.client.read-timeout=20s", "spring.http.client.ssl.bundle=test") + .run((context) -> { + ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class); + assertThat(settings.redirects()).isEqualTo(Redirects.DONT_FOLLOW); + assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(10)); + assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(20)); + assertThat(settings.sslBundle().getKey().getAlias()).isEqualTo("alias1"); + }); + } + + private List sslPropertyValues() { + List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/autoconfigure/ssl/"; + propertyValues.add("spring.ssl.bundle.pem.test.key.alias=alias1"); + propertyValues.add("spring.ssl.bundle.pem.test.truststore.type=PKCS12"); + propertyValues.add("spring.ssl.bundle.pem.test.truststore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("spring.ssl.bundle.pem.test.truststore.private-key=" + location + "rsa-key.pem"); + return propertyValues; + } + + @Test + void whenReactiveWebApplicationBeansAreNotConfigured() { + new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations) + .run((context) -> assertThat(context).doesNotHaveBean(ClientHttpRequestFactoryBuilder.class) + .doesNotHaveBean(ClientHttpRequestFactorySettings.class)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientPropertiesTests.java new file mode 100644 index 000000000000..16d66577ab72 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientPropertiesTests.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.autoconfigure.http.client; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.http.client.HttpClientProperties.Factory; +import org.springframework.boot.http.client.HttpComponentsClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.JettyClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ReactorClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HttpClientProperties}. + * + * @author Phillip Webb + */ +class HttpClientPropertiesTests { + + @Nested + class FactoryTests { + + @Test + void httpComponentsBuilder() { + assertThat(Factory.HTTP_COMPONENTS.builder()) + .isInstanceOf(HttpComponentsClientHttpRequestFactoryBuilder.class); + } + + @Test + void jettyBuilder() { + assertThat(Factory.JETTY.builder()).isInstanceOf(JettyClientHttpRequestFactoryBuilder.class); + } + + @Test + void reactorBuilder() { + assertThat(Factory.REACTOR.builder()).isInstanceOf(ReactorClientHttpRequestFactoryBuilder.class); + } + + @Test + void jdkBuilder() { + assertThat(Factory.JDK.builder()).isInstanceOf(JdkClientHttpRequestFactoryBuilder.class); + } + + @Test + void simpleBuilder() { + assertThat(Factory.SIMPLE.builder()).isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class); + } + + } + +} 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 3306c4031e6b..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/influx/InfluxDbAutoConfigurationTests.java +++ /dev/null @@ -1,115 +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.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 - * @deprecated since 3.2.0 for removal in 3.4.0 - */ -@SuppressWarnings("removal") -@Deprecated(since = "3.2.0", forRemoval = true) -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 - @SuppressWarnings("removal") - 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/integration/IntegrationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java index f4c7bca9e87a..f23e0fd71cf1 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 @@ -32,6 +32,8 @@ import io.rsocket.transport.netty.client.TcpClientTransport; 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 reactor.core.publisher.Mono; import org.springframework.beans.DirectFieldAccessor; @@ -54,9 +56,11 @@ import org.springframework.boot.sql.init.DatabaseInitializationMode; import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.annotation.ServiceActivator; @@ -84,6 +88,7 @@ import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.messaging.support.GenericMessage; import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.test.util.ReflectionTestUtils; @@ -98,6 +103,7 @@ * @author Artem Bilan * @author Stephane Nicoll * @author Vedran Pavic + * @author Yong-Hyun Kim */ class IntegrationAutoConfigurationTests { @@ -521,6 +527,19 @@ void integrationManagementInstrumentedWithObservation() { }); } + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void integrationVirtualThreadsEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class)) + .run((context) -> assertThat(context).hasSingleBean(TaskScheduler.class) + .getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class) + .isInstanceOf(SimpleAsyncTaskScheduler.class) + .satisfies((taskScheduler) -> SimpleAsyncTaskExecutorAssert + .assertThat((SimpleAsyncTaskExecutor) taskScheduler) + .usesVirtualThreads())); + } + @Configuration(proxyBeanMethods = false) static class CustomMBeanExporter { 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 3e8dcf01c8f3..29dde38a9714 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ 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.ser.DefaultSerializerProvider; import com.fasterxml.jackson.databind.util.StdDateFormat; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import org.assertj.core.api.InstanceOfAssertFactories; @@ -318,7 +319,8 @@ void moduleBeansAndWellKnownModulesAreRegisteredWithTheObjectMapperBuilder() { this.contextRunner.withUserConfiguration(ModuleConfig.class).run((context) -> { ObjectMapper objectMapper = context.getBean(Jackson2ObjectMapperBuilder.class).build(); assertThat(context.getBean(CustomModule.class).getOwners()).contains(objectMapper); - assertThat(objectMapper.canSerialize(Baz.class)).isTrue(); + assertThat(((DefaultSerializerProvider) objectMapper.getSerializerProviderInstance()) + .hasSerializerFor(Baz.class, null)).isTrue(); }); } 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 1e2cbca31086..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. @@ -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/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/OracleUcpDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java index a31f631d3b92..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 @@ -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/jms/JmsAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java index 7ea7a0446e16..7df4a2ad49c7 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. @@ -44,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; @@ -63,6 +64,7 @@ * @author Aurélien Leboulanger * @author Eddú Meléndez * @author Vedran Pavic + * @author Lasse Wulff */ class JmsAutoConfigurationTests { @@ -71,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 @@ -123,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) @@ -151,7 +175,9 @@ void testJmsListenerContainerFactoryWithCustomSettings() { .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.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10", + "spring.jms.subscription-durable=true", "spring.jms.client-id=exampleId", + "spring.jms.listener.max-messages-per-task=5") .run(this::testJmsListenerContainerFactoryWithCustomSettings); } @@ -163,6 +189,9 @@ private void testJmsListenerContainerFactoryWithCustomSettings(AssertableApplica assertThat(container.getConcurrentConsumers()).isEqualTo(2); assertThat(container.getMaxConcurrentConsumers()).isEqualTo(10); assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L); + assertThat(container).hasFieldOrPropertyWithValue("maxMessagesPerTask", 5); + assertThat(container.isSubscriptionDurable()).isTrue(); + assertThat(container.getClientId()).isEqualTo("exampleId"); } @Test 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 2815e3e2ff50..b6f31fbf7784 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,14 +48,14 @@ class ActiveMQAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class)); @Test - void brokerIsLocalhostByDefault() { + void brokerIsEmbeddedByDefault() { this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(CachingConnectionFactory.class).hasBean("jmsConnectionFactory"); CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); assertThat(connectionFactory.getTargetConnectionFactory()).isInstanceOf(ActiveMQConnectionFactory.class); assertThat(((ActiveMQConnectionFactory) connectionFactory.getTargetConnectionFactory()).getBrokerURL()) - .isEqualTo("tcp://localhost:61616"); + .isEqualTo("vm://localhost?broker.persistent=false"); }); } 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 3c4ee27987c1..78c45042ef68 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 @@ -31,13 +31,15 @@ */ class ActiveMQPropertiesTests { + private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false"; + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; private final ActiveMQProperties properties = new ActiveMQProperties(); @Test - void getBrokerUrlIsLocalhostByDefault() { - assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + void getBrokerUrlIsEmbeddedByDefault() { + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_EMBEDDED_BROKER_URL); } @Test @@ -46,6 +48,19 @@ void getBrokerUrlUseExplicitBrokerUrl() { assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); } + @Test + void getBrokerUrlWithEmbeddedSetToFalse() { + this.properties.getEmbedded().setEnabled(false); + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + } + + @Test + void getExplicitBrokerUrlAlwaysWins() { + this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); + this.properties.getEmbedded().setEnabled(false); + assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); + } + @Test void setTrustAllPackages() { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); 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 b03df4aa2103..cbf31a98aa17 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 @@ -48,6 +48,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; @@ -375,6 +376,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); @@ -431,7 +453,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); @@ -500,4 +522,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..577e4a4b19a6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,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..8e8025f3e6f5 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. @@ -29,6 +29,7 @@ import org.jooq.TransactionProvider; import org.jooq.TransactionalRunnable; import org.jooq.impl.DataSourceConnectionProvider; +import org.jooq.impl.DefaultDSLContext; import org.jooq.impl.DefaultExecuteListenerProvider; import org.junit.jupiter.api.Test; @@ -55,6 +56,7 @@ * @author Andy Wilkinson * @author Stephane Nicoll * @author Dmytro Nosan + * @author Dennis Melzer */ class JooqAutoConfigurationTests { @@ -180,6 +182,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 @@ -193,6 +215,12 @@ void transactionProviderFromConfigurationCustomizerOverridesTransactionProviderB }); } + @Test + void autoConfiguredJooqConfigurationCanBeUsedToCreateCustomDslContext() { + this.contextRunner.withUserConfiguration(CustomDslContextConfiguration.class, JooqDataSourceConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(DSLContext.class).hasBean("customDslContext")); + } + static class AssertFetch implements TransactionalRunnable { private final DSLContext dsl; @@ -254,6 +282,16 @@ TransactionProvider transactionProvider() { } + @Configuration(proxyBeanMethods = false) + static class CustomJooqExceptionTranslatorConfiguration { + + @Bean + ExceptionTranslatorExecuteListener jooqExceptionTranslator() { + return new CustomJooqExceptionTranslator(); + } + + } + @Configuration(proxyBeanMethods = false) static class CustomTransactionProviderFromCustomizerConfiguration { @@ -274,6 +312,16 @@ PlatformTransactionManager transactionManager(DataSource dataSource) { } + @Configuration(proxyBeanMethods = false) + static class CustomDslContextConfiguration { + + @Bean + DSLContext customDslContext(org.jooq.Configuration configuration) { + return new DefaultDSLContext(configuration); + } + + } + @Order(100) static class TestExecuteListenerProvider implements ExecuteListenerProvider { @@ -303,4 +351,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..f5390c0f5e61 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. @@ -39,7 +39,10 @@ * Tests for {@link JooqExceptionTranslator} * * @author Andy Wilkinson + * @deprecated since 3.3.0 for removal in 3.5.0 */ +@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/KafkaAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationIntegrationTests.java index 8b0668ffe8dd..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,6 +28,7 @@ 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; @@ -115,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"); 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 4adc774e8d7f..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. @@ -396,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() { @@ -456,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", @@ -485,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") @@ -496,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") @@ -757,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)); }); } @@ -772,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/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/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index ac15c4880e33..87acfb7078c7 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,10 +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; @@ -82,6 +85,7 @@ * @author Evgeniy Cheban * @author Moritz Halbritter * @author Phillip Webb + * @author Ahmed Ashour */ @ExtendWith(OutputCaptureExtension.class) class LiquibaseAutoConfigurationTests { @@ -226,6 +230,7 @@ void defaultValues() { 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); })); } @@ -233,7 +238,7 @@ void defaultValues() { void overrideContexts() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.liquibase.contexts:test, production") - .run(assertLiquibase((liquibase) -> assertThat(liquibase.getContexts()).isEqualTo("test, production"))); + .run(assertLiquibase((liquibase) -> assertThat(liquibase.getContexts()).isEqualTo("test,production"))); } @Test @@ -386,7 +391,7 @@ void logging(CapturedOutput output) { void overrideLabelFilter() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) .withPropertyValues("spring.liquibase.label-filter:test, production") - .run(assertLiquibase((liquibase) -> assertThat(liquibase.getLabelFilter()).isEqualTo("test, production"))); + .run(assertLiquibase((liquibase) -> assertThat(liquibase.getLabelFilter()).isEqualTo("test,production"))); } @Test @@ -411,6 +416,14 @@ void overrideShowSummaryOutput() { })); } + @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 @SuppressWarnings("unchecked") void testOverrideParameters() { @@ -522,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); @@ -658,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 index a46ff51ec9ba..f6533a5a9bf7 100644 --- 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 @@ -21,10 +21,12 @@ 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; @@ -45,6 +47,11 @@ 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/mail/MailSenderAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java index 6fd720cbbaa0..27f4baddffdf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.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/MongoPropertiesClientSettingsBuilderCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizerTests.java deleted file mode 100644 index b50a3c1e72f4..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesClientSettingsBuilderCustomizerTests.java +++ /dev/null @@ -1,234 +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.autoconfigure.mongo; - -import java.util.Arrays; -import java.util.List; - -import com.mongodb.MongoClientSettings; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; -import com.mongodb.connection.ClusterType; -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 for removal in 3.3.0 - */ -@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"); - assertThat(settings.getClusterSettings().getRequiredClusterType()).isEqualTo(ClusterType.REPLICA_SET); - } - - @Test - void replicaSetCanBeNull() { - this.properties.setReplicaSetName(null); - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getClusterSettings().getRequiredReplicaSetName()).isNull(); - assertThat(settings.getClusterSettings().getRequiredClusterType()).isEqualTo(ClusterType.UNKNOWN); - } - - @Test - void replicaSetCanBeEmptyString() { - this.properties.setReplicaSetName(""); - MongoClientSettings settings = customizeSettings(); - assertThat(settings.getClusterSettings().getRequiredReplicaSetName()).isNull(); - assertThat(settings.getClusterSettings().getRequiredClusterType()).isEqualTo(ClusterType.UNKNOWN); - } - - @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 6fe08c4d0a02..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 @@ -230,7 +230,6 @@ void customizerWithTransportSettingsOverridesAutoConfig() { assertThat(settings.getApplicationName()).isEqualTo("custom-transport-settings"); assertThat(settings.getTransportSettings()) .isSameAs(SimpleTransportSettingsCustomizerConfig.transportSettings); - assertThat(settings.getStreamFactoryFactory()).isNull(); }); } 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 869607e7e875..95b1f92f14f9 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. @@ -41,6 +41,7 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration; import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -54,6 +55,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; @@ -69,6 +71,7 @@ * @author Phillip Webb * @author Dave Syer * @author Stephane Nicoll + * @author Yanming Zhou */ abstract class AbstractJpaAutoConfigurationTests { @@ -205,6 +208,18 @@ void customJpaProperties() { }); } + @Test + void usesManuallyDefinedLocalContainerEntityManagerFactoryBeanUsingBuilder() { + this.contextRunner.withPropertyValues("spring.jpa.properties.a=b") + .withUserConfiguration(TestConfigurationWithEntityManagerFactoryBuilder.class) + .run((context) -> { + LocalContainerEntityManagerFactoryBean factoryBean = context + .getBean(LocalContainerEntityManagerFactoryBean.class); + Map map = factoryBean.getJpaPropertyMap(); + assertThat(map).containsEntry("configured", "manually").containsEntry("a", "b"); + }); + } + @Test void usesManuallyDefinedLocalContainerEntityManagerFactoryBeanIfAvailable() { this.contextRunner.withUserConfiguration(TestConfigurationWithLocalContainerEntityManagerFactoryBean.class) @@ -279,6 +294,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); @@ -368,6 +393,17 @@ static class ManualOpenEntityManagerInViewInterceptor extends OpenEntityManagerI } + @Configuration(proxyBeanMethods = false) + static class TestConfigurationWithEntityManagerFactoryBuilder extends TestConfiguration { + + @Bean + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder, + DataSource dataSource) { + return builder.dataSource(dataSource).properties(Map.of("configured", "manually")).build(); + } + + } + @Configuration(proxyBeanMethods = false) static class TestConfigurationWithLocalContainerEntityManagerFactoryBean extends TestConfiguration { @@ -423,6 +459,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/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/PulsarAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarAutoConfigurationTests.java index 7fa599bce464..fdc10771c1f9 100644 --- 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 @@ -67,9 +67,13 @@ import org.springframework.pulsar.core.PulsarProducerFactory; import org.springframework.pulsar.core.PulsarReaderFactory; import org.springframework.pulsar.core.PulsarTemplate; +import org.springframework.pulsar.core.PulsarTopicBuilder; 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.reactive.config.DefaultReactivePulsarListenerContainerFactory; +import org.springframework.pulsar.transaction.PulsarAwareTransactionManager; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -124,6 +128,7 @@ void autoConfiguresBeans() { .hasSingleBean(PulsarConnectionDetails.class) .hasSingleBean(DefaultPulsarClientFactory.class) .hasSingleBean(PulsarClient.class) + .hasSingleBean(PulsarTopicBuilder.class) .hasSingleBean(PulsarAdministration.class) .hasSingleBean(DefaultSchemaResolver.class) .hasSingleBean(DefaultTopicResolver.class) @@ -139,6 +144,12 @@ void autoConfiguresBeans() { .hasSingleBean(PulsarReaderEndpointRegistry.class)); } + @Test + void topicDefaultsCanBeDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarTopicBuilder.class)); + } + @Nested class ProducerFactoryTests { @@ -217,7 +228,17 @@ void injectsExpectedBeans() { "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))); + .hasFieldOrPropertyWithValue("topicResolver", context.getBean(TopicResolver.class)) + .extracting("topicBuilder") + .isNotNull()); + } + + @Test + void hasNoTopicBuilderWhenTopicDefaultsAreDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat(context).getBean(DefaultPulsarProducerFactory.class) + .extracting("topicBuilder") + .isNull()); } @ParameterizedTest @@ -313,7 +334,7 @@ void whenHasUseDefinedProducerInterceptorsInjectsBeansInCorrectOrder() { @Test void whenNoPropertiesEnablesObservation() { this.contextRunner.run((context) -> assertThat(context).getBean(PulsarTemplate.class) - .hasFieldOrPropertyWithValue("observationEnabled", true)); + .hasFieldOrPropertyWithValue("observationEnabled", false)); } @Test @@ -330,6 +351,13 @@ void whenObservationsDisabledDoesNotEnableObservation() { .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 { @@ -366,7 +394,18 @@ void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { @Test void injectsExpectedBeans() { this.contextRunner.run((context) -> assertThat(context).getBean(DefaultPulsarConsumerFactory.class) - .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class))); + .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class)) + .extracting("topicBuilder") + .isNotNull()); + } + + @Test + void hasNoTopicBuilderWhenTopicDefaultsAreDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat(context).getBean(DefaultPulsarConsumerFactory.class) + .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class)) + .extracting("topicBuilder") + .isNull()); } @Test @@ -483,7 +522,7 @@ void whenHasCustomProperties() { void whenNoPropertiesEnablesObservation() { this.contextRunner .run((context) -> assertThat(context).getBean(ConcurrentPulsarListenerContainerFactory.class) - .hasFieldOrPropertyWithValue("containerProperties.observationEnabled", true)); + .hasFieldOrPropertyWithValue("containerProperties.observationEnabled", false)); } @Test @@ -525,6 +564,67 @@ void whenVirtualThreadsAreEnabledOnJava20AndEarlierListenerContainerShouldNotUse }); } + @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(); + }); + } + + @Test + void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + this.contextRunner.withUserConfiguration(ListenerContainerFactoryCustomizersConfig.class) + .run((context) -> assertThat(context).getBean(ConcurrentPulsarListenerContainerFactory.class) + .hasFieldOrPropertyWithValue("containerProperties.subscriptionName", ":bar:foo")); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ListenerContainerFactoryCustomizersConfig { + + @Bean + @Order(50) + PulsarContainerFactoryCustomizer> customizerIgnored() { + return (containerFactory) -> { + throw new IllegalStateException("should-not-have-matched"); + }; + } + + @Bean + @Order(200) + PulsarContainerFactoryCustomizer> customizerFoo() { + return (containerFactory) -> appendToSubscriptionName(containerFactory, ":foo"); + } + + @Bean + @Order(100) + PulsarContainerFactoryCustomizer> customizerBar() { + return (containerFactory) -> appendToSubscriptionName(containerFactory, ":bar"); + } + + private void appendToSubscriptionName(ConcurrentPulsarListenerContainerFactory containerFactory, + String valueToAppend) { + String subscriptionName = containerFactory.getContainerProperties().getSubscriptionName(); + String updatedValue = (subscriptionName != null) ? subscriptionName + valueToAppend : valueToAppend; + containerFactory.getContainerProperties().setSubscriptionName(updatedValue); + } + + } + } @Nested @@ -543,11 +643,21 @@ void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { @Test void injectsExpectedBeans() { this.contextRunner.run((context) -> assertThat(context).getBean(DefaultPulsarReaderFactory.class) - .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class))); + .hasFieldOrPropertyWithValue("pulsarClient", context.getBean(PulsarClient.class)) + .extracting("topicBuilder") + .isNotNull()); } @Test - void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { + void hasNoTopicBuilderWhenTopicDefaultsAreDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat(context).getBean(DefaultPulsarReaderFactory.class) + .extracting("topicBuilder") + .isNull()); + } + + @Test + void whenHasUserDefinedReaderBuilderCustomizersAppliesInCorrectOrder() { this.contextRunner.withPropertyValues("spring.pulsar.reader.name=fromPropsCustomizer") .withUserConfiguration(ReaderBuilderCustomizersConfig.class) .run((context) -> { @@ -584,6 +694,13 @@ void whenVirtualThreadsAreEnabledOnJava20AndEarlierReaderShouldNotUseVirtualThre }); } + @Test + void whenHasUserDefinedFactoryCustomizersAppliesInCorrectOrder() { + this.contextRunner.withUserConfiguration(ReaderContainerFactoryCustomizersConfig.class) + .run((context) -> assertThat(context).getBean(DefaultPulsarReaderContainerFactory.class) + .hasFieldOrPropertyWithValue("containerProperties.readerListener", ":bar:foo")); + } + @TestConfiguration(proxyBeanMethods = false) static class ReaderBuilderCustomizersConfig { @@ -601,6 +718,71 @@ ReaderBuilderCustomizer customizerBar() { } + @TestConfiguration(proxyBeanMethods = false) + static class ReaderContainerFactoryCustomizersConfig { + + @Bean + @Order(50) + PulsarContainerFactoryCustomizer> customizerIgnored() { + return (containerFactory) -> { + throw new IllegalStateException("should-not-have-matched"); + }; + } + + @Bean + @Order(200) + PulsarContainerFactoryCustomizer> customizerFoo() { + return (containerFactory) -> appendToReaderListener(containerFactory, ":foo"); + } + + @Bean + @Order(100) + PulsarContainerFactoryCustomizer> customizerBar() { + return (containerFactory) -> appendToReaderListener(containerFactory, ":bar"); + } + + private void appendToReaderListener(DefaultPulsarReaderContainerFactory containerFactory, + String valueToAppend) { + Object readerListener = containerFactory.getContainerProperties().getReaderListener(); + String updatedValue = (readerListener != null) ? readerListener + valueToAppend : valueToAppend; + containerFactory.getContainerProperties().setReaderListener(updatedValue); + } + + } + + } + + @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 index a1136b11ba29..a0763714ca5c 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,26 @@ 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; @@ -44,14 +49,17 @@ import org.springframework.pulsar.core.PulsarAdministration; import org.springframework.pulsar.core.PulsarClientBuilderCustomizer; import org.springframework.pulsar.core.PulsarClientFactory; +import org.springframework.pulsar.core.PulsarTopicBuilder; 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; /** @@ -61,6 +69,7 @@ * @author Alexander Preuß * @author Soby Chacko * @author Phillip Webb + * @author Swamy Mavuri */ class PulsarConfigurationTests { @@ -77,6 +86,15 @@ void whenHasUserDefinedConnectionDetailsBeanDoesNotAutoConfigureBean() { .isSameAs(customConnectionDetails)); } + @Test + void whenHasUserDefinedContainerFactoryCustomizersBeanDoesNotAutoConfigureBean() { + PulsarContainerFactoryCustomizers customizers = mock(PulsarContainerFactoryCustomizers.class); + this.contextRunner + .withBean("customContainerFactoryCustomizers", PulsarContainerFactoryCustomizers.class, () -> customizers) + .run((context) -> assertThat(context).getBean(PulsarContainerFactoryCustomizers.class) + .isSameAs(customizers)); + } + @Nested class ClientTests { @@ -113,6 +131,38 @@ void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { }); } + @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 { @@ -280,6 +330,46 @@ void whenHasDefaultsTypeMappingAddsToSchemaResolver() { } + @Nested + class TopicBuilderTests { + + private final ApplicationContextRunner contextRunner = PulsarConfigurationTests.this.contextRunner; + + @Test + void whenHasUserDefinedBeanDoesNotAutoConfigureBean() { + PulsarTopicBuilder topicBuilder = mock(PulsarTopicBuilder.class); + this.contextRunner.withBean("customPulsarTopicBuilder", PulsarTopicBuilder.class, () -> topicBuilder) + .run((context) -> assertThat(context).getBean(PulsarTopicBuilder.class).isSameAs(topicBuilder)); + } + + @Test + void whenHasDefaultsTopicDisabledPropertyDoesNotCreateBean() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarTopicBuilder.class)); + } + + @Test + void whenHasDefaultsTenantAndNamespaceAppliedToTopicBuilder() { + List properties = new ArrayList<>(); + properties.add("spring.pulsar.defaults.topic.tenant=my-tenant"); + properties.add("spring.pulsar.defaults.topic.namespace=my-namespace"); + this.contextRunner.withPropertyValues(properties.toArray(String[]::new)) + .run((context) -> assertThat(context).getBean(PulsarTopicBuilder.class) + .asInstanceOf(InstanceOfAssertFactories.type(PulsarTopicBuilder.class)) + .satisfies((topicBuilder) -> { + assertThat(topicBuilder).hasFieldOrPropertyWithValue("defaultTenant", "my-tenant"); + assertThat(topicBuilder).hasFieldOrPropertyWithValue("defaultNamespace", "my-namespace"); + })); + } + + @Test + void beanHasScopePrototype() { + this.contextRunner.run((context) -> assertThat(context.getBean(PulsarTopicBuilder.class)) + .isNotSameAs(context.getBean(PulsarTopicBuilder.class))); + } + + } + @Nested class FunctionAdministrationTests { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizersTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizersTests.java new file mode 100644 index 000000000000..8b03635db225 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/pulsar/PulsarContainerFactoryCustomizersTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.pulsar.config.ConcurrentPulsarListenerContainerFactory; +import org.springframework.pulsar.config.DefaultPulsarReaderContainerFactory; +import org.springframework.pulsar.config.ListenerContainerFactory; +import org.springframework.pulsar.config.PulsarContainerFactory; +import org.springframework.pulsar.config.PulsarListenerContainerFactory; +import org.springframework.pulsar.core.PulsarConsumerFactory; +import org.springframework.pulsar.listener.PulsarContainerProperties; +import org.springframework.pulsar.reactive.config.DefaultReactivePulsarListenerContainerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PulsarContainerFactoryCustomizers}. + * + * @author Chris Bono + */ +class PulsarContainerFactoryCustomizersTests { + + @Test + void customizeWithNullCustomizersShouldDoNothing() { + PulsarContainerFactory containerFactory = mock(PulsarContainerFactory.class); + new PulsarContainerFactoryCustomizers(null).customize(containerFactory); + then(containerFactory).shouldHaveNoInteractions(); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + void customizeSimplePulsarContainerFactory() { + PulsarContainerFactoryCustomizers customizers = new PulsarContainerFactoryCustomizers( + Collections.singletonList(new SimplePulsarContainerFactoryCustomizer())); + PulsarContainerProperties containerProperties = new PulsarContainerProperties(); + ConcurrentPulsarListenerContainerFactory pulsarContainerFactory = new ConcurrentPulsarListenerContainerFactory<>( + mock(PulsarConsumerFactory.class), containerProperties); + customizers.customize(pulsarContainerFactory); + assertThat(pulsarContainerFactory.getContainerProperties().getSubscriptionName()).isEqualTo("my-subscription"); + } + + @Test + void customizeShouldCheckGeneric() { + List> list = new ArrayList<>(); + list.add(new TestCustomizer<>()); + list.add(new TestPulsarListenersContainerFactoryCustomizer()); + list.add(new TestConcurrentPulsarListenerContainerFactoryCustomizer()); + PulsarContainerFactoryCustomizers customizers = new PulsarContainerFactoryCustomizers(list); + + customizers.customize(mock(PulsarContainerFactory.class)); + assertThat(list.get(0).getCount()).isOne(); + assertThat(list.get(1).getCount()).isZero(); + assertThat(list.get(2).getCount()).isZero(); + + customizers.customize(mock(ConcurrentPulsarListenerContainerFactory.class)); + assertThat(list.get(0).getCount()).isEqualTo(2); + assertThat(list.get(1).getCount()).isOne(); + assertThat(list.get(2).getCount()).isOne(); + + customizers.customize(mock(DefaultReactivePulsarListenerContainerFactory.class)); + assertThat(list.get(0).getCount()).isEqualTo(3); + assertThat(list.get(1).getCount()).isEqualTo(2); + assertThat(list.get(2).getCount()).isOne(); + + customizers.customize(mock(DefaultPulsarReaderContainerFactory.class)); + assertThat(list.get(0).getCount()).isEqualTo(4); + assertThat(list.get(1).getCount()).isEqualTo(2); + assertThat(list.get(2).getCount()).isOne(); + } + + static class SimplePulsarContainerFactoryCustomizer + implements PulsarContainerFactoryCustomizer> { + + @Override + public void customize(ConcurrentPulsarListenerContainerFactory containerFactory) { + containerFactory.getContainerProperties().setSubscriptionName("my-subscription"); + } + + } + + /** + * Test customizer that will match all {@link PulsarListenerContainerFactory}. + */ + static class TestCustomizer> implements PulsarContainerFactoryCustomizer { + + private int count; + + @Override + public void customize(T pulsarContainerFactory) { + this.count++; + } + + int getCount() { + return this.count; + } + + } + + /** + * Test customizer that will match both + * {@link ConcurrentPulsarListenerContainerFactory} and + * {@link DefaultReactivePulsarListenerContainerFactory} as they both extend + * {@link ListenerContainerFactory}. + */ + static class TestPulsarListenersContainerFactoryCustomizer extends TestCustomizer> { + + } + + /** + * Test customizer that will match only + * {@link ConcurrentPulsarListenerContainerFactory}. + */ + static class TestConcurrentPulsarListenerContainerFactoryCustomizer + extends TestCustomizer> { + + } + +} 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 index 04b8c699c394..5e8ca006fcd2 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -34,22 +35,31 @@ 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 + * @author Vedran Pavic */ class PulsarPropertiesMapperTests { @@ -60,6 +70,8 @@ void customizeClientBuilderWhenHasNoAuthentication() { properties.getClient().setConnectionTimeout(Duration.ofSeconds(1)); properties.getClient().setOperationTimeout(Duration.ofSeconds(2)); properties.getClient().setLookupTimeout(Duration.ofSeconds(3)); + properties.getClient().getThreads().setIo(3); + properties.getClient().getThreads().setListener(10); ClientBuilder builder = mock(ClientBuilder.class); new PulsarPropertiesMapper(properties).customizeClientBuilder(builder, new PropertiesPulsarConnectionDetails(properties)); @@ -67,6 +79,8 @@ void customizeClientBuilderWhenHasNoAuthentication() { then(builder).should().connectionTimeout(1000, TimeUnit.MILLISECONDS); then(builder).should().operationTimeout(2000, TimeUnit.MILLISECONDS); then(builder).should().lookupTimeout(3000, TimeUnit.MILLISECONDS); + then(builder).should().ioThreads(3); + then(builder).should().listenerThreads(10); } @Test @@ -84,6 +98,26 @@ void customizeClientBuilderWhenHasAuthentication() throws UnsupportedAuthenticat 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(); @@ -95,6 +129,31 @@ void customizeClientBuilderWhenHasConnectionDetails() { 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(); @@ -163,6 +222,16 @@ void customizeProducerBuilder() { 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() { @@ -192,13 +261,19 @@ void customizeConsumerBuilder() { void customizeContainerProperties() { PulsarProperties properties = new PulsarProperties(); properties.getConsumer().getSubscription().setType(SubscriptionType.Shared); + properties.getConsumer().getSubscription().setName("my-subscription"); properties.getListener().setSchemaType(SchemaType.AVRO); - properties.getListener().setObservationEnabled(false); + properties.getListener().setConcurrency(10); + 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.getSubscriptionName()).isEqualTo("my-subscription"); assertThat(containerProperties.getSchemaType()).isEqualTo(SchemaType.AVRO); - assertThat(containerProperties.isObservationEnabled()).isFalse(); + assertThat(containerProperties.getConcurrency()).isEqualTo(10); + assertThat(containerProperties.isObservationEnabled()).isTrue(); + assertThat(containerProperties.transactions().isEnabled()).isTrue(); } @Test 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 index 48c17247a4f9..72fbdd0e73f4 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.time.Duration; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.pulsar.client.api.CompressionType; @@ -29,14 +30,18 @@ import org.apache.pulsar.client.api.SubscriptionMode; import org.apache.pulsar.client.api.SubscriptionType; import org.apache.pulsar.common.schema.SchemaType; +import org.assertj.core.extractor.Extractors; 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 org.springframework.pulsar.core.PulsarTopicBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -48,10 +53,12 @@ * @author Christophe Bornet * @author Soby Chacko * @author Phillip Webb + * @author Swamy Mavuri + * @author Vedran Pavic */ class PulsarPropertiesTests { - private PulsarProperties bindPropeties(Map map) { + private PulsarProperties bindProperties(Map map) { return new Binder(new MapConfigurationPropertySource(map)).bind("spring.pulsar", PulsarProperties.class).get(); } @@ -65,7 +72,7 @@ void bind() { 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 = bindPropeties(map).getClient(); + 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)); @@ -77,11 +84,51 @@ 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 = bindPropeties(map).getClient(); + PulsarProperties.Client properties = bindProperties(map).getClient(); assertThat(properties.getAuthentication().getPluginClassName()).isEqualTo("com.example.MyAuth"); assertThat(properties.getAuthentication().getParam()).containsEntry("token", "1234"); } + @Test + void bindThread() { + Map map = new HashMap<>(); + map.put("spring.pulsar.client.threads.io", "3"); + map.put("spring.pulsar.client.threads.listener", "10"); + PulsarProperties.Client properties = bindProperties(map).getClient(); + assertThat(properties.getThreads().getIo()).isEqualTo(3); + assertThat(properties.getThreads().getListener()).isEqualTo(10); + } + + @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 @@ -98,7 +145,7 @@ void bind() { 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 = bindPropeties(map).getAdmin(); + 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)); @@ -110,7 +157,7 @@ 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 = bindPropeties(map).getAdmin(); + PulsarProperties.Admin properties = bindProperties(map).getAdmin(); assertThat(properties.getAuthentication().getPluginClassName()).isEqualTo(this.authPluginClassName); assertThat(properties.getAuthentication().getParam()).containsEntry("token", this.authToken); } @@ -118,7 +165,7 @@ void bindAuthentication() { } @Nested - class DefaultsProperties { + class DefaultsTypeMappingProperties { @Test void bindWhenNoTypeMappings() { @@ -132,7 +179,7 @@ void bindWhenTypeMappingsWithTopicsOnly() { 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 = bindPropeties(map).getDefaults(); + 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); @@ -143,7 +190,7 @@ 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 = bindPropeties(map).getDefaults(); + PulsarProperties.Defaults properties = bindProperties(map).getDefaults(); TypeMapping expected = new TypeMapping(TestMessage.class, null, new SchemaInfo(SchemaType.JSON, null)); assertThat(properties.getTypeMappings()).containsExactly(expected); } @@ -154,7 +201,7 @@ void bindWhenTypeMappingsWithTopicAndSchema() { 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 = bindPropeties(map).getDefaults(); + PulsarProperties.Defaults properties = bindProperties(map).getDefaults(); TypeMapping expected = new TypeMapping(TestMessage.class, "foo-topic", new SchemaInfo(SchemaType.JSON, null)); assertThat(properties.getTypeMappings()).containsExactly(expected); @@ -166,7 +213,7 @@ void bindWhenTypeMappingsWithKeyValueSchema() { 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 = bindPropeties(map).getDefaults(); + 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); @@ -177,7 +224,7 @@ 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(() -> bindPropeties(map)) + assertThatExceptionOfType(BindException.class).isThrownBy(() -> bindProperties(map)) .havingRootCause() .withMessageContaining("schemaType must not be null"); } @@ -187,7 +234,7 @@ 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(() -> bindPropeties(map)) + assertThatExceptionOfType(BindException.class).isThrownBy(() -> bindProperties(map)) .havingRootCause() .withMessageContaining("schemaType 'NONE' not supported"); } @@ -198,7 +245,7 @@ void bindWhenMessageKeyTypeSetOnNonKeyValueSchemaThrowsException() { 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(() -> bindPropeties(map)) + assertThatExceptionOfType(BindException.class).isThrownBy(() -> bindProperties(map)) .havingRootCause() .withMessageContaining("messageKeyType can only be set when schemaType is KEY_VALUE"); } @@ -208,6 +255,32 @@ record TestMessage(String value) { } + @Nested + class DefaultsTenantNamespaceProperties { + + @Test + void bindWhenValuesNotSpecified() { + PulsarTopicBuilder defaultTopicBuilder = new PulsarTopicBuilder(); + assertThat(new PulsarProperties().getDefaults().getTopic()).satisfies((defaults) -> { + assertThat(defaults.getTenant()) + .isEqualTo(Extractors.byName("defaultTenant").apply(defaultTopicBuilder)); + assertThat(defaults.getNamespace()) + .isEqualTo(Extractors.byName("defaultNamespace").apply(defaultTopicBuilder)); + }); + } + + @Test + void bindWhenValuesSpecified() { + Map map = new HashMap<>(); + map.put("spring.pulsar.defaults.topic.tenant", "my-tenant"); + map.put("spring.pulsar.defaults.topic.namespace", "my-namespace"); + PulsarProperties.Defaults.Topic properties = bindProperties(map).getDefaults().getTopic(); + assertThat(properties.getTenant()).isEqualTo("my-tenant"); + assertThat(properties.getNamespace()).isEqualTo("my-namespace"); + } + + } + @Nested class FunctionProperties { @@ -225,7 +298,7 @@ void bind() { 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 = bindPropeties(props).getFunction(); + PulsarProperties.Function properties = bindProperties(props).getFunction(); assertThat(properties.isFailFast()).isFalse(); assertThat(properties.isPropagateFailures()).isFalse(); assertThat(properties.isPropagateStopFailures()).isTrue(); @@ -251,7 +324,7 @@ void bind() { 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 = bindPropeties(map).getProducer(); + 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)); @@ -289,7 +362,7 @@ void bind() { 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 = bindPropeties(map).getConsumer(); + PulsarProperties.Consumer properties = bindProperties(map).getConsumer(); assertThat(properties.getName()).isEqualTo("my-consumer"); assertThat(properties.getSubscription()).satisfies((subscription) -> { assertThat(subscription.getName()).isEqualTo("my-subscription"); @@ -320,10 +393,12 @@ class ListenerProperties { void bind() { Map map = new HashMap<>(); map.put("spring.pulsar.listener.schema-type", "avro"); - map.put("spring.pulsar.listener.observation-enabled", "false"); - PulsarProperties.Listener properties = bindPropeties(map).getListener(); + map.put("spring.pulsar.listener.concurrency", "10"); + map.put("spring.pulsar.listener.observation-enabled", "true"); + PulsarProperties.Listener properties = bindProperties(map).getListener(); assertThat(properties.getSchemaType()).isEqualTo(SchemaType.AVRO); - assertThat(properties.isObservationEnabled()).isFalse(); + assertThat(properties.getConcurrency()).isEqualTo(10); + assertThat(properties.isObservationEnabled()).isTrue(); } } @@ -339,7 +414,7 @@ void bind() { 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 = bindPropeties(map).getReader(); + PulsarProperties.Reader properties = bindProperties(map).getReader(); assertThat(properties.getName()).isEqualTo("my-reader"); assertThat(properties.getTopics()).containsExactly("my-topic"); assertThat(properties.getSubscriptionName()).isEqualTo("my-subscription"); @@ -355,9 +430,22 @@ class TemplateProperties { @Test void bind() { Map map = new HashMap<>(); - map.put("spring.pulsar.template.observations-enabled", "false"); - PulsarProperties.Template properties = bindPropeties(map).getTemplate(); - assertThat(properties.isObservationsEnabled()).isFalse(); + 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 index 4f3ab011ea2e..933b9a27174e 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; +import org.springframework.pulsar.config.ConcurrentPulsarListenerContainerFactory; import org.springframework.pulsar.core.DefaultSchemaResolver; import org.springframework.pulsar.core.DefaultTopicResolver; import org.springframework.pulsar.core.PulsarAdministration; +import org.springframework.pulsar.core.PulsarTopicBuilder; import org.springframework.pulsar.core.SchemaResolver; import org.springframework.pulsar.core.TopicResolver; import org.springframework.pulsar.reactive.config.DefaultReactivePulsarListenerContainerFactory; @@ -114,6 +116,7 @@ void whenCustomPulsarListenerAnnotationProcessorDefinedAutoConfigurationIsSkippe void autoConfiguresBeans() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PulsarConfiguration.class) .hasSingleBean(PulsarClient.class) + .hasSingleBean(PulsarTopicBuilder.class) .hasSingleBean(PulsarAdministration.class) .hasSingleBean(DefaultSchemaResolver.class) .hasSingleBean(DefaultTopicResolver.class) @@ -128,6 +131,12 @@ void autoConfiguresBeans() { .hasSingleBean(ReactivePulsarListenerEndpointRegistry.class)); } + @Test + void topicDefaultsCanBeDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(PulsarTopicBuilder.class)); + } + @Test @SuppressWarnings("rawtypes") void injectsExpectedBeansIntoReactivePulsarClient() { @@ -177,9 +186,17 @@ void injectsExpectedBeans() { assertThat(senderFactory) .extracting("topicResolver", InstanceOfAssertFactories.type(TopicResolver.class)) .isSameAs(context.getBean(TopicResolver.class)); + assertThat(senderFactory).extracting("topicBuilder").isNotNull(); }); } + @Test + void hasNoTopicBuilderWhenTopicDefaultsAreDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat((DefaultReactivePulsarSenderFactory) context + .getBean(DefaultReactivePulsarSenderFactory.class)).extracting("topicBuilder").isNull()); + } + @Test void injectsExpectedBeansIntoReactiveMessageSenderCache() { ProducerCacheProvider provider = mock(ProducerCacheProvider.class); @@ -252,16 +269,30 @@ class ConsumerFactoryTests { @Test void injectsExpectedBeans() { ReactivePulsarClient client = mock(ReactivePulsarClient.class); + PulsarTopicBuilder topicBuilder = mock(PulsarTopicBuilder.class); this.contextRunner.withBean("customReactivePulsarClient", ReactivePulsarClient.class, () -> client) + .withBean("customTopicBuilder", PulsarTopicBuilder.class, () -> topicBuilder) .run((context) -> { ReactivePulsarConsumerFactory consumerFactory = context .getBean(DefaultReactivePulsarConsumerFactory.class); assertThat(consumerFactory) .extracting("reactivePulsarClient", InstanceOfAssertFactories.type(ReactivePulsarClient.class)) .isSameAs(client); + assertThat(consumerFactory) + .extracting("topicBuilder", InstanceOfAssertFactories.type(PulsarTopicBuilder.class)) + .isSameAs(topicBuilder); }); } + @Test + void hasNoTopicBuilderWhenTopicDefaultsAreDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat( + (ReactivePulsarConsumerFactory) context.getBean(DefaultReactivePulsarConsumerFactory.class)) + .extracting("topicBuilder") + .isNull()); + } + @Test void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { this.contextRunner.withPropertyValues("spring.pulsar.consumer.name=fromPropsCustomizer") @@ -352,6 +383,45 @@ void injectsExpectedBeans() { }); } + @Test + void whenHasUserDefinedFactoryCustomizersAppliesInCorrectOrder() { + this.contextRunner.withUserConfiguration(ListenerContainerFactoryCustomizersConfig.class) + .run((context) -> assertThat(context).getBean(DefaultReactivePulsarListenerContainerFactory.class) + .hasFieldOrPropertyWithValue("containerProperties.subscriptionName", ":bar:foo")); + } + + @TestConfiguration(proxyBeanMethods = false) + static class ListenerContainerFactoryCustomizersConfig { + + @Bean + @Order(50) + PulsarContainerFactoryCustomizer> customizerIgnored() { + return (containerFactory) -> { + throw new IllegalStateException("should-not-have-matched"); + }; + } + + @Bean + @Order(200) + PulsarContainerFactoryCustomizer> customizerFoo() { + return (containerFactory) -> appendToSubscriptionName(containerFactory, ":foo"); + } + + @Bean + @Order(100) + PulsarContainerFactoryCustomizer> customizerBar() { + return (containerFactory) -> appendToSubscriptionName(containerFactory, ":bar"); + } + + private void appendToSubscriptionName(DefaultReactivePulsarListenerContainerFactory containerFactory, + String valueToAppend) { + String subscriptionName = containerFactory.getContainerProperties().getSubscriptionName(); + String updatedValue = (subscriptionName != null) ? subscriptionName + valueToAppend : valueToAppend; + containerFactory.getContainerProperties().setSubscriptionName(updatedValue); + } + + } + } @Nested @@ -362,17 +432,29 @@ class ReaderFactoryTests { @Test void injectsExpectedBeans() { ReactivePulsarClient client = mock(ReactivePulsarClient.class); + PulsarTopicBuilder topicBuilder = mock(PulsarTopicBuilder.class); this.contextRunner.withPropertyValues("spring.pulsar.reader.name=test-reader") .withBean("customReactivePulsarClient", ReactivePulsarClient.class, () -> client) + .withBean("customPulsarTopicBuilder", PulsarTopicBuilder.class, () -> topicBuilder) .run((context) -> { DefaultReactivePulsarReaderFactory readerFactory = context .getBean(DefaultReactivePulsarReaderFactory.class); assertThat(readerFactory) .extracting("reactivePulsarClient", InstanceOfAssertFactories.type(ReactivePulsarClient.class)) .isSameAs(client); + assertThat(readerFactory) + .extracting("topicBuilder", InstanceOfAssertFactories.type(PulsarTopicBuilder.class)) + .isSameAs(topicBuilder); }); } + @Test + void hasNoTopicBuilderWhenTopicDefaultsAreDisabled() { + this.contextRunner.withPropertyValues("spring.pulsar.defaults.topic.enabled=false") + .run((context) -> assertThat((DefaultReactivePulsarReaderFactory) context + .getBean(DefaultReactivePulsarReaderFactory.class)).extracting("topicBuilder").isNull()); + } + @Test void whenHasUserDefinedCustomizersAppliesInCorrectOrder() { this.contextRunner.withPropertyValues("spring.pulsar.reader.name=fromPropsCustomizer") 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 index df078b21a354..1c45f1aa9c09 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 Chris Bono * @author Phillip Webb + * @author Vedran Pavic */ class PulsarReactivePropertiesMapperTests { @@ -119,11 +120,15 @@ void customizeMessageConsumerBuilder() { void customizeContainerProperties() { PulsarProperties properties = new PulsarProperties(); properties.getConsumer().getSubscription().setType(SubscriptionType.Shared); + properties.getConsumer().getSubscription().setName("my-subscription"); properties.getListener().setSchemaType(SchemaType.AVRO); + properties.getListener().setConcurrency(10); ReactivePulsarContainerProperties containerProperties = new ReactivePulsarContainerProperties<>(); new PulsarReactivePropertiesMapper(properties).customizeContainerProperties(containerProperties); assertThat(containerProperties.getSubscriptionType()).isEqualTo(SubscriptionType.Shared); + assertThat(containerProperties.getSubscriptionName()).isEqualTo("my-subscription"); assertThat(containerProperties.getSchemaType()).isEqualTo(SchemaType.AVRO); + assertThat(containerProperties.getConcurrency()).isEqualTo(10); } @Test 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/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 e8165ee18916..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. @@ -39,10 +39,13 @@ 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; @@ -52,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; @@ -63,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; @@ -92,6 +97,7 @@ * @author Anastasiia Losieva * @author Mushtaq Ahmed * @author Roman Golovin + * @author Yan Kardziyaka */ class ReactiveOAuth2ResourceServerAutoConfigurationTests { @@ -476,8 +482,8 @@ void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfProper .run((context) -> { assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class); - validate(jwt(), reactiveJwtDecoder, (validators) -> assertThat(validators).singleElement() - .isInstanceOf(JwtTimestampValidator.class)); + validate(jwt(), reactiveJwtDecoder, + (validators) -> assertThat(validators).hasSize(2).noneSatisfy(audClaimValidator())); }); } @@ -626,6 +632,70 @@ void customValidatorWhenInvalid() throws Exception { }); } + @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); @@ -807,4 +877,18 @@ JwtClaimValidator customJwtClaimValidator() { } + @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 b7aa1a5ec67b..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. @@ -38,9 +38,12 @@ 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; @@ -51,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; @@ -84,6 +89,7 @@ * @author HaiTao Zhang * @author Mushtaq Ahmed * @author Roman Golovin + * @author Yan Kardziyaka */ class OAuth2ResourceServerAutoConfigurationTests { @@ -424,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); }); } @@ -494,8 +500,8 @@ void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfProper .run((context) -> { assertThat(context).hasSingleBean(JwtDecoder.class); JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - validate(jwt(), jwtDecoder, (validators) -> assertThat(validators).singleElement() - .isInstanceOf(JwtTimestampValidator.class)); + validate(jwt(), jwtDecoder, + (validators) -> assertThat(validators).hasSize(2).noneSatisfy(audClaimValidator())); }); } @@ -640,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(); @@ -795,4 +863,18 @@ JwtClaimValidator customJwtClaimValidator() { } + @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/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/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfigurationTests.java index 90651911bf10..37ceafca1adb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerWebSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.context.annotation.Import; import org.springframework.core.annotation.Order; import org.springframework.security.config.BeanIds; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -35,7 +36,7 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter; import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter; @@ -163,7 +164,11 @@ static class TestSecurityFilterChainConfiguration { @Bean @Order(1) SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + OAuth2AuthorizationServerConfigurer authorizationServer = OAuth2AuthorizationServerConfigurer + .authorizationServer(); + http.securityMatcher(authorizationServer.getEndpointsMatcher()) + .with(authorizationServer, Customizer.withDefaults()); + http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); return http.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 5c8b4ae27da4..07aab477a6b3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 Madhura Bhave * @author Moritz Halbritter * @author Lasse Lindqvist + * @author Scott Frederick */ class Saml2RelyingPartyAutoConfigurationTests { @@ -94,30 +95,30 @@ void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent( RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()) + assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"); - assertThat(registration.getAssertingPartyDetails().getEntityId()) + assertThat(registration.getAssertingPartyMetadata().getEntityId()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"); assertThat(registration.getAssertionConsumerServiceLocation()) .isEqualTo("{baseUrl}/login/saml2/foo-entity-id"); assertThat(registration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding()) .isEqualTo(Saml2MessageBinding.POST); - assertThat(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()).isFalse(); + assertThat(registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()).isFalse(); assertThat(registration.getSigningX509Credentials()).hasSize(1); assertThat(registration.getDecryptionX509Credentials()).hasSize(1); - assertThat(registration.getAssertingPartyDetails().getVerificationX509Credentials()).isNotNull(); + assertThat(registration.getAssertingPartyMetadata().getVerificationX509Credentials()).isNotNull(); assertThat(registration.getEntityId()).isEqualTo("{baseUrl}/saml2/foo-entity-id"); assertThat(registration.getSingleLogoutServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php"); assertThat(registration.getSingleLogoutServiceResponseLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/"); assertThat(registration.getSingleLogoutServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()) + assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php"); - assertThat(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()) + assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/"); - assertThat(registration.getAssertingPartyDetails().getSingleLogoutServiceBinding()) + assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding()) .isEqualTo(Saml2MessageBinding.POST); }); } @@ -162,7 +163,7 @@ void autoconfigurationShouldUseBindingFromMetadataUrlIfPresent() throws Exceptio RelyingPartyRegistrationRepository repository = context .getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding()) .isEqualTo(Saml2MessageBinding.POST); }); } @@ -181,7 +182,7 @@ void autoconfigurationWhenMetadataUrlAndPropertyPresentShouldUseBindingFromPrope RelyingPartyRegistrationRepository repository = context .getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding()) .isEqualTo(Saml2MessageBinding.REDIRECT); }); } @@ -192,7 +193,7 @@ void autoconfigurationWhenNoMetadataUrlOrPropertyPresentShouldUseRedirectBinding this.contextRunner.withPropertyValues(getPropertyValuesWithoutSsoBinding()).run((context) -> { RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding()) .isEqualTo(Saml2MessageBinding.REDIRECT); }); } @@ -268,11 +269,43 @@ void signRequestShouldApplyIfMetadataUriIsSet() throws Exception { RelyingPartyRegistrationRepository repository = context .getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()).isTrue(); + assertThat(registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()).isTrue(); }); } } + @Test + void autoconfigurationWithInvalidPrivateKeyShouldFail() { + this.contextRunner.withPropertyValues( + PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/certificate-location", + PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/certificate-location", + PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + PREFIX + ".foo.assertingparty.singlesignon.binding=post", + PREFIX + ".foo.assertingparty.singlesignon.sign-request=false", + PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:saml/certificate-location") + .run((context) -> assertThat(context).hasFailed() + .getFailure() + .rootCause() + .hasMessageContaining("Missing private key or unrecognized format")); + } + + @Test + void autoconfigurationWithInvalidCertificateShouldFail() { + this.contextRunner.withPropertyValues( + PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/private-key-location", + PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/private-key-location", + PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + PREFIX + ".foo.assertingparty.singlesignon.binding=post", + PREFIX + ".foo.assertingparty.singlesignon.sign-request=false", + PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:saml/certificate-location") + .run((context) -> assertThat(context).hasFailed() + .getFailure() + .rootCause() + .hasMessageContaining("Missing certificates or unrecognized format")); + } + private void testMultipleProviders(String specifiedEntityId, String expected) throws Exception { try (MockWebServer server = new MockWebServer()) { server.start(); @@ -290,7 +323,7 @@ private void testMultipleProviders(String specifiedEntityId, String expected) th RelyingPartyRegistrationRepository repository = context .getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration.getAssertingPartyDetails().getEntityId()).isEqualTo(expected); + assertThat(registration.getAssertingPartyMetadata().getEntityId()).isEqualTo(expected); }); } } 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/UserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java index 9cffa54313ec..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 @@ -23,10 +23,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.logging.LogLevel; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; @@ -183,7 +181,6 @@ void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresentAndUsernameC this.contextRunner .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class)) .withPropertyValues("spring.security.user.name=alice") - .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) .run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class))); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java index eabb3a4655c6..392ec235aeac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java @@ -156,7 +156,7 @@ void sessionCookieConfigurationIsAppliedToAutoConfiguredCookieSerializer() { .withPropertyValues("server.servlet.session.cookie.name=sid", "server.servlet.session.cookie.domain=spring", "server.servlet.session.cookie.path=/test", "server.servlet.session.cookie.httpOnly=false", "server.servlet.session.cookie.secure=false", "server.servlet.session.cookie.maxAge=10s", - "server.servlet.session.cookie.sameSite=strict") + "server.servlet.session.cookie.sameSite=strict", "server.servlet.session.cookie.partitioned=true") .run((context) -> { DefaultCookieSerializer cookieSerializer = context.getBean(DefaultCookieSerializer.class); assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieName", "sid"); @@ -166,6 +166,7 @@ void sessionCookieConfigurationIsAppliedToAutoConfiguredCookieSerializer() { assertThat(cookieSerializer).hasFieldOrPropertyWithValue("useSecureCookie", false); assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieMaxAge", 10); assertThat(cookieSerializer).hasFieldOrPropertyWithValue("sameSite", "Strict"); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("partitioned", true); }); } 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/BundleContentPropertyTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java index 5df13c9a9429..c03165b70800 100644 --- 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 @@ -16,14 +16,21 @@ 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 org.junit.jupiter.api.io.TempDir; + +import org.springframework.boot.io.ApplicationResourceLoader; +import org.springframework.core.io.ResourceLoader; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.spy; /** * Tests for {@link BundleContentProperty}. @@ -38,9 +45,6 @@ class BundleContentPropertyTests { -----END CERTIFICATE----- """; - @TempDir - Path temp; - @Test void isPemContentWhenValueIsPemTextReturnsTrue() { BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT); @@ -74,21 +78,33 @@ void hasValueWhenHasEmptyValueReturnsFalse() { @Test void toWatchPathWhenNotPathThrowsException() { BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT); - assertThatIllegalStateException().isThrownBy(property::toWatchPath) + assertThatIllegalStateException().isThrownBy(() -> property.toWatchPath(ApplicationResourceLoader.get())) .withMessage("Unable to convert value of property 'name' to a path"); } @Test - void toWatchPathWhenPathReturnsPath() { - Path file = this.temp.toAbsolutePath().resolve("file.txt"); + 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(ApplicationResourceLoader.get())).isEqualTo(file); + } + + @Test + void toWatchPathUsesResourceLoader() 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); + ResourceLoader resourceLoader = spy(ApplicationResourceLoader.get()); + assertThat(property.toWatchPath(resourceLoader)).isEqualTo(file); + then(resourceLoader).should(atLeastOnce()).getResource(file.toString()); } @Test void shouldThrowBundleContentNotWatchableExceptionIfContentIsNotWatchable() { BundleContentProperty property = new BundleContentProperty("name", "https://example.com/"); - assertThatExceptionOfType(BundleContentNotWatchableException.class).isThrownBy(property::toWatchPath) + assertThatExceptionOfType(BundleContentNotWatchableException.class) + .isThrownBy(() -> property.toWatchPath(ApplicationResourceLoader.get())) .withMessageContaining("Only 'file:' resources are watchable"); } 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 d6b770a3d927..66d1c6d1d667 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 +25,15 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.ssl.SslBundle; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; import org.springframework.util.function.ThrowingConsumer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.spy; /** * Tests for {@link PropertiesSslBundle}. @@ -137,6 +142,22 @@ void getWithPemSslBundlePropertiesWhenVerifyKeyStoreWithNoMatchThrowsException() .withMessageContaining("Private key in keystore matches none of the certificates"); } + @Test + void getWithResourceLoader() { + 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"); + ResourceLoader resourceLoader = spy(new DefaultResourceLoader()); + SslBundle bundle = PropertiesSslBundle.get(properties, resourceLoader); + assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias")); + then(resourceLoader).should(atLeastOnce()) + .getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt"); + then(resourceLoader).should(atLeastOnce()) + .getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem"); + } + private Consumer storeContainingCertAndKey(String keyAlias) { return ThrowingConsumer.of((keyStore) -> { assertThat(keyStore).isNotNull(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfigurationTests.java index 06cbc412829b..87e2e0c3d1e6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,29 +53,22 @@ void sslBundlesCreatedWithNoConfiguration() { @Test void sslBundlesCreatedWithCertificates() { List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/autoconfigure/ssl/"; propertyValues.add("spring.ssl.bundle.pem.first.key.alias=alias1"); propertyValues.add("spring.ssl.bundle.pem.first.key.password=secret1"); - propertyValues.add( - "spring.ssl.bundle.pem.first.keystore.certificate=classpath:org/springframework/boot/autoconfigure/ssl/rsa-cert.pem"); - propertyValues.add( - "spring.ssl.bundle.pem.first.keystore.private-key=classpath:org/springframework/boot/autoconfigure/ssl/rsa-key.pem"); + propertyValues.add("spring.ssl.bundle.pem.first.keystore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("spring.ssl.bundle.pem.first.keystore.private-key=" + location + "rsa-key.pem"); propertyValues.add("spring.ssl.bundle.pem.first.keystore.type=PKCS12"); propertyValues.add("spring.ssl.bundle.pem.first.truststore.type=PKCS12"); - propertyValues.add( - "spring.ssl.bundle.pem.first.truststore.certificate=classpath:org/springframework/boot/autoconfigure/ssl/rsa-cert.pem"); - propertyValues.add( - "spring.ssl.bundle.pem.first.truststore.private-key=classpath:org/springframework/boot/autoconfigure/ssl/rsa-key.pem"); + propertyValues.add("spring.ssl.bundle.pem.first.truststore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("spring.ssl.bundle.pem.first.truststore.private-key=" + location + "rsa-key.pem"); propertyValues.add("spring.ssl.bundle.pem.second.key.alias=alias2"); propertyValues.add("spring.ssl.bundle.pem.second.key.password=secret2"); - propertyValues.add( - "spring.ssl.bundle.pem.second.keystore.certificate=classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem"); - propertyValues.add( - "spring.ssl.bundle.pem.second.keystore.private-key=classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem"); + propertyValues.add("spring.ssl.bundle.pem.second.keystore.certificate=" + location + "ed25519-cert.pem"); + propertyValues.add("spring.ssl.bundle.pem.second.keystore.private-key=" + location + "ed25519-key.pem"); propertyValues.add("spring.ssl.bundle.pem.second.keystore.type=PKCS12"); - propertyValues.add( - "spring.ssl.bundle.pem.second.truststore.certificate=classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem"); - propertyValues.add( - "spring.ssl.bundle.pem.second.truststore.private-key=classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem"); + propertyValues.add("spring.ssl.bundle.pem.second.truststore.certificate=" + location + "ed25519-cert.pem"); + propertyValues.add("spring.ssl.bundle.pem.second.truststore.private-key=" + location + "ed25519-key.pem"); propertyValues.add("spring.ssl.bundle.pem.second.truststore.type=PKCS12"); this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> { assertThat(context).hasSingleBean(SslBundles.class); @@ -102,14 +95,12 @@ void sslBundlesCreatedWithCertificates() { @Test void sslBundlesCreatedWithCustomSslBundle() { List propertyValues = new ArrayList<>(); + String location = "classpath:org/springframework/boot/autoconfigure/ssl/"; propertyValues.add("custom.ssl.key.alias=alias1"); propertyValues.add("custom.ssl.key.password=secret1"); - propertyValues - .add("custom.ssl.keystore.certificate=classpath:org/springframework/boot/autoconfigure/ssl/rsa-cert.pem"); - propertyValues.add( - "custom.ssl.keystore.keystore.private-key=classpath:org/springframework/boot/autoconfigure/ssl/rsa-key.pem"); - propertyValues - .add("custom.ssl.truststore.certificate=classpath:org/springframework/boot/autoconfigure/ssl/rsa-cert.pem"); + propertyValues.add("custom.ssl.keystore.certificate=" + location + "rsa-cert.pem"); + propertyValues.add("custom.ssl.keystore.keystore.private-key=" + location + "rsa-key.pem"); + propertyValues.add("custom.ssl.truststore.certificate=" + location + "rsa-cert.pem"); propertyValues.add("custom.ssl.keystore.type=PKCS12"); propertyValues.add("custom.ssl.truststore.type=PKCS12"); this.contextRunner.withUserConfiguration(CustomSslBundleConfiguration.class) 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 index 759bb474609b..dafabd8801b1 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; import org.springframework.boot.ssl.SslBundleRegistry; +import org.springframework.core.io.DefaultResourceLoader; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -31,6 +33,8 @@ import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; /** @@ -44,6 +48,8 @@ class SslPropertiesBundleRegistrarTests { private FileWatcher fileWatcher; + private DefaultResourceLoader resourceLoader; + private SslProperties properties; private SslBundleRegistry registry; @@ -52,7 +58,8 @@ class SslPropertiesBundleRegistrarTests { void setUp() { this.properties = new SslProperties(); this.fileWatcher = Mockito.mock(FileWatcher.class); - this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher); + this.resourceLoader = spy(new DefaultResourceLoader()); + this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher, this.resourceLoader); this.registry = Mockito.mock(SslBundleRegistry.class); } @@ -85,6 +92,21 @@ void shouldWatchPemBundles() { .watch(assertArg((set) -> pathEndingWith(set, "rsa-cert.pem", "rsa-key.pem")), any()); } + @Test + void shouldUseResourceLoader() { + PemSslBundleProperties pem = new PemSslBundleProperties(); + 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); + DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry(); + this.registrar.registerBundles(registry); + registry.getBundle("bundle1").createSslContext(); + then(this.resourceLoader).should(atLeastOnce()) + .getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem"); + then(this.resourceLoader).should(atLeastOnce()) + .getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem"); + } + @Test void shouldFailIfPemKeystoreCertificateIsEmbedded() { PemSslBundleProperties pem = new PemSslBundleProperties(); 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 be2bbb9cb0c3..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 @@ -32,8 +32,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder; -import org.springframework.boot.task.TaskExecutorBuilder; -import org.springframework.boot.task.TaskExecutorCustomizer; import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -51,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; /** @@ -60,9 +57,9 @@ * @author Stephane Nicoll * @author Camille Vienot * @author Moritz Halbritter + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) -@SuppressWarnings("removal") class TaskExecutionAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -71,44 +68,12 @@ class TaskExecutionAutoConfigurationTests { @Test void shouldSupplyBeans() { this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); assertThat(context).hasSingleBean(ThreadPoolTaskExecutorBuilder.class); assertThat(context).hasSingleBean(ThreadPoolTaskExecutor.class); assertThat(context).hasSingleBean(SimpleAsyncTaskExecutorBuilder.class); }); } - @Test - @SuppressWarnings("deprecation") - void shouldNotSupplyThreadPoolTaskExecutorBuilderIfCustomTaskExecutorBuilderIsPresent() { - this.contextRunner.withBean(TaskExecutorBuilder.class, TaskExecutorBuilder::new).run((context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); - assertThat(context).doesNotHaveBean(ThreadPoolTaskExecutorBuilder.class); - assertThat(context).hasSingleBean(ThreadPoolTaskExecutor.class); - }); - } - - @Test - void taskExecutorBuilderShouldApplyCustomSettings() { - 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) -> { - 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("waitForTasksToCompleteOnShutdown", true); - assertThat(taskExecutor).hasFieldOrPropertyWithValue("awaitTerminationMillis", 30000L); - assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); - })); - } - @Test void simpleAsyncTaskExecutorBuilderShouldReadProperties() { this.contextRunner @@ -125,34 +90,26 @@ void simpleAsyncTaskExecutorBuilderShouldReadProperties() { @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.shutdown.await-termination=true", - "spring.task.execution.shutdown.await-termination-period=30s", - "spring.task.execution.thread-name-prefix=mytest-") + 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-"); })); } - @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); - }); - } - @Test void threadPoolTaskExecutorBuilderWhenHasCustomBuilderShouldUseCustomBuilder() { this.contextRunner.withUserConfiguration(CustomThreadPoolTaskExecutorBuilderConfig.class).run((context) -> { @@ -162,16 +119,6 @@ void threadPoolTaskExecutorBuilderWhenHasCustomBuilderShouldUseCustomBuilder() { }); } - @Test - @SuppressWarnings("deprecation") - void taskExecutorBuilderShouldUseTaskDecorator() { - this.contextRunner.withUserConfiguration(TaskDecoratorConfig.class).run((context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); - ThreadPoolTaskExecutor executor = context.getBean(TaskExecutorBuilder.class).build(); - assertThat(executor).extracting("taskDecorator").isSameAs(context.getBean(TaskDecorator.class)); - }); - } - @Test void threadPoolTaskExecutorBuilderShouldUseTaskDecorator() { this.contextRunner.withUserConfiguration(TaskDecoratorConfig.class).run((context) -> { @@ -274,26 +221,6 @@ void whenVirtualThreadsAreEnabledAndCustomTaskExecutorIsDefinedThenSimpleAsyncTa }); } - @Test - @SuppressWarnings("deprecation") - 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); - }); - } - - @Test - @SuppressWarnings("deprecation") - void threadPoolTaskExecutorBuilderShouldApplyCustomizer() { - this.contextRunner.withUserConfiguration(TaskExecutorCustomizerConfig.class).run((context) -> { - TaskExecutorCustomizer customizer = context.getBean(TaskExecutorCustomizer.class); - ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutorBuilder.class).build(); - then(customizer).should().customize(executor); - }); - } - @Test void enableAsyncUsesAutoConfiguredOneByDefault() { this.contextRunner.withPropertyValues("spring.task.execution.thread-name-prefix=task-test-") @@ -318,35 +245,6 @@ void enableAsyncUsesAutoConfiguredOneByDefaultEvenThoughSchedulingIsConfigured() }); } - @Test - void customTaskExecutorBuilderOverridesThreadPoolTaskExecutorBuilder() { - this.contextRunner.withUserConfiguration(CustomTaskExecutorBuilderConfig.class).run((context) -> { - ThreadPoolTaskExecutor bean = context.getBean(ThreadPoolTaskExecutor.class); - assertThat(bean.getThreadNamePrefix()).isEqualTo("CustomTaskExecutorBuilderConfig-"); - }); - } - - @Test - void threadPoolTaskExecutorBuilderAppliesTaskExecutorCustomizer() { - this.contextRunner - .withBean(TaskExecutorCustomizer.class, - () -> (taskExecutor) -> taskExecutor.setThreadNamePrefix("custom-prefix-")) - .run((context) -> { - ThreadPoolTaskExecutor bean = context.getBean(ThreadPoolTaskExecutor.class); - assertThat(bean.getThreadNamePrefix()).isEqualTo("custom-prefix-"); - }); - } - - @SuppressWarnings("deprecation") - private ContextConsumer assertTaskExecutor( - Consumer taskExecutor) { - return (context) -> { - assertThat(context).hasSingleBean(TaskExecutorBuilder.class); - TaskExecutorBuilder builder = context.getBean(TaskExecutorBuilder.class); - taskExecutor.accept(builder.build()); - }; - } - private ContextConsumer assertThreadPoolTaskExecutor( Consumer taskExecutor) { return (context) -> { @@ -379,20 +277,6 @@ private String virtualThreadName(SimpleAsyncTaskExecutor taskExecutor) throws In return thread.getName(); } - @Configuration(proxyBeanMethods = false) - static class CustomTaskExecutorBuilderConfig { - - @SuppressWarnings("deprecation") - private final org.springframework.boot.task.TaskExecutorBuilder taskExecutorBuilder = new org.springframework.boot.task.TaskExecutorBuilder() - .threadNamePrefix("CustomTaskExecutorBuilderConfig-"); - - @Bean - TaskExecutorBuilder customTaskExecutorBuilder() { - return this.taskExecutorBuilder; - } - - } - @Configuration(proxyBeanMethods = false) static class CustomThreadPoolTaskExecutorBuilderConfig { @@ -405,16 +289,6 @@ ThreadPoolTaskExecutorBuilder customThreadPoolTaskExecutorBuilder() { } - @Configuration(proxyBeanMethods = false) - static class TaskExecutorCustomizerConfig { - - @Bean - TaskExecutorCustomizer mockTaskExecutorCustomizer() { - return mock(TaskExecutorCustomizer.class); - } - - } - @Configuration(proxyBeanMethods = false) static class TaskDecoratorConfig { 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 0171c72681dd..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 @@ -36,8 +36,6 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder; import org.springframework.boot.task.SimpleAsyncTaskSchedulerCustomizer; -import org.springframework.boot.task.TaskSchedulerBuilder; -import org.springframework.boot.task.TaskSchedulerCustomizer; import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder; import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -59,7 +57,6 @@ * @author Stephane Nicoll * @author Moritz Halbritter */ -@SuppressWarnings("removal") class TaskSchedulingAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -80,24 +77,11 @@ void noSchedulingDoesNotExposeScheduledBeanLazyInitializationExcludeFilter() { @Test void shouldSupplyBeans() { this.contextRunner.withUserConfiguration(SchedulingConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(TaskSchedulerBuilder.class); assertThat(context).hasSingleBean(ThreadPoolTaskSchedulerBuilder.class); assertThat(context).hasSingleBean(ThreadPoolTaskScheduler.class); }); } - @Test - @SuppressWarnings("deprecation") - void shouldNotSupplyThreadPoolTaskSchedulerBuilderIfCustomTaskSchedulerBuilderIsPresent() { - this.contextRunner.withUserConfiguration(SchedulingConfiguration.class) - .withBean(TaskSchedulerBuilder.class, TaskSchedulerBuilder::new) - .run((context) -> { - assertThat(context).hasSingleBean(TaskSchedulerBuilder.class); - assertThat(context).doesNotHaveBean(ThreadPoolTaskSchedulerBuilder.class); - assertThat(context).hasSingleBean(ThreadPoolTaskScheduler.class); - }); - } - @Test void enableSchedulingWithNoTaskExecutorAutoConfiguresOne() { this.contextRunner @@ -170,18 +154,6 @@ void simpleAsyncTaskSchedulerBuilderShouldApplyCustomizers() { }); } - @Test - void enableSchedulingWithNoTaskExecutorAppliesTaskSchedulerCustomizers() { - this.contextRunner.withPropertyValues("spring.task.scheduling.thread-name-prefix=scheduling-test-") - .withUserConfiguration(SchedulingConfiguration.class, TaskSchedulerCustomizerConfiguration.class) - .run((context) -> { - assertThat(context).hasSingleBean(TaskExecutor.class); - TestBean bean = context.getBean(TestBean.class); - assertThat(bean.latch.await(30, TimeUnit.SECONDS)).isTrue(); - assertThat(bean.threadNames).allMatch((name) -> name.contains("customized-scheduler-")); - }); - } - @Test void enableSchedulingWithNoTaskExecutorAppliesCustomizers() { this.contextRunner.withPropertyValues("spring.task.scheduling.thread-name-prefix=scheduling-test-") @@ -262,16 +234,6 @@ ScheduledExecutorService customScheduledExecutorService() { } - @Configuration(proxyBeanMethods = false) - static class TaskSchedulerCustomizerConfiguration { - - @Bean - TaskSchedulerCustomizer testTaskSchedulerCustomizer() { - return ((taskScheduler) -> taskScheduler.setThreadNamePrefix("customized-scheduler-")); - } - - } - @Configuration(proxyBeanMethods = false) static class ThreadPoolTaskSchedulerCustomizerConfiguration { 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 index 24bf90e0b27b..16c271f64af4 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.Collections; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -42,7 +43,7 @@ void autoConfiguresTransactionManagerCustomizers() { this.contextRunner.run((context) -> { TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class); assertThat(customizers).extracting("customizers") - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .hasSize(2) .hasAtLeastOneElementOfType(TransactionProperties.class) .hasAtLeastOneElementOfType(ExecutionListenersTransactionManagerCustomizer.class); @@ -54,7 +55,9 @@ void autoConfiguredTransactionManagerCustomizersBacksOff() { this.contextRunner.withUserConfiguration(CustomTransactionManagerCustomizersConfiguration.class) .run((context) -> { TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class); - assertThat(customizers).extracting("customizers").asList().isEmpty(); + assertThat(customizers).extracting("customizers") + .asInstanceOf(InstanceOfAssertFactories.LIST) + .isEmpty(); }); } 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 396b00987650..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. @@ -46,8 +46,8 @@ void customizeShouldCheckGeneric() { list.add(new TestCustomizer<>()); list.add(new TestJtaCustomizer()); TransactionManagerCustomizers customizers = TransactionManagerCustomizers.of(list); - customizers.customize((TransactionManager) mock(PlatformTransactionManager.class)); - customizers.customize((TransactionManager) mock(JtaTransactionManager.class)); + customizers.customize(mock(PlatformTransactionManager.class)); + customizers.customize(mock(JtaTransactionManager.class)); assertThat(list.get(0).getCount()).isEqualTo(2); assertThat(list.get(1).getCount()).isOne(); } 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 641c45dc220f..33d2182fee76 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +157,8 @@ static class Wrapper implements SmartValidator { } @Override - public boolean supports(Class clazz) { - return this.delegate.supports(clazz); + public boolean supports(Class type) { + return this.delegate.supports(type); } @Override 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 ff8b377d2d95..231df5b4c927 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. @@ -46,6 +46,8 @@ 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.server.MimeMappings; +import org.springframework.boot.web.server.MimeMappings.Mapping; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.unit.DataSize; @@ -66,6 +68,7 @@ * @author Rafiullah Hamedy * @author Chris Bono * @author Parviz Rozikov + * @author Lasse Wulff */ @DirtiesUrlFactories class ServerPropertiesTests { @@ -139,7 +142,6 @@ void testTomcatBinding() { assertThat(tomcat.getRemoteip().getProtocolHeader()).isEqualTo("X-Forwarded-Protocol"); assertThat(tomcat.getRemoteip().getInternalProxies()).isEqualTo("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); assertThat(tomcat.getRemoteip().getTrustedProxies()).isEqualTo("proxy1|proxy2|proxy3"); - assertThat(tomcat.isRejectIllegalHeader()).isFalse(); assertThat(tomcat.getBackgroundProcessorDelay()).hasSeconds(10); assertThat(tomcat.getRelaxedPathChars()).containsExactly('|', '<'); assertThat(tomcat.getRelaxedQueryChars()).containsExactly('^', '|'); @@ -182,6 +184,20 @@ void testContextPathWithLeadingAndTrailingWhitespaceAndContextWithSpace() { assertThat(this.properties.getServlet().getContextPath()).isEqualTo("/assets /copy"); } + @Test + void testDefaultMimeMapping() { + assertThat(this.properties.getMimeMappings()).isEmpty(); + } + + @Test + 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 void testCustomizeUriEncoding() { bind("server.tomcat.uri-encoding", "US-ASCII"); @@ -405,13 +421,6 @@ void tomcatInternalProxiesMatchesDefault() { .isEqualTo(new RemoteIpValve().getInternalProxies()); } - @Test - @SuppressWarnings("removal") - void tomcatRejectIllegalHeaderMatchesProtocolDefault() throws Exception { - assertThat(getDefaultProtocol()).hasFieldOrPropertyWithValue("rejectIllegalHeader", - this.properties.getTomcat().isRejectIllegalHeader()); - } - @Test void tomcatUseRelativeRedirectsDefaultsToFalse() { assertThat(this.properties.getTomcat().isUseRelativeRedirects()).isFalse(); @@ -448,6 +457,15 @@ void jettyMaxHttpFormPostSizeMatchesDefault() { .isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormContentSize()); } + @Test + void jettyMaxFormKeysMatchesDefault() { + JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0); + JettyWebServer jetty = (JettyWebServer) jettyFactory.getWebServer(); + Server server = jetty.getServer(); + assertThat(this.properties.getJetty().getMaxFormKeys()) + .isEqualTo(((ServletContextHandler) server.getHandler()).getMaxFormKeys()); + } + @Test void undertowMaxHttpPostSizeMatchesDefault() { assertThat(this.properties.getUndertow().getMaxHttpPostSize().toBytes()) 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 index 576d6e45808b..b5bb408f5992 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,14 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration; 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.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.test.util.ReflectionTestUtils; @@ -49,7 +51,7 @@ class RestClientAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, HttpClientAutoConfiguration.class)); @Test void shouldSupplyBeans() { @@ -156,6 +158,19 @@ void restClientWhenHasCustomMessageConvertersShouldHaveMessageConverters() { }); } + @Test + void whenHasFactoryProperty() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withUserConfiguration(RestClientConfig.class) + .withPropertyValues("spring.http.client.factory=simple") + .run((context) -> { + assertThat(context).hasSingleBean(RestClient.class); + RestClient restClient = context.getBean(RestClient.class); + assertThat(restClient).extracting("clientRequestFactory") + .isInstanceOf(SimpleClientHttpRequestFactory.class); + }); + } + @Configuration(proxyBeanMethods = false) static class CodecConfiguration { 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 582752925976..43d718ab41c0 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.AutoConfigurations; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -36,6 +37,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.mock.http.client.MockClientHttpRequest; @@ -56,8 +58,8 @@ */ class RestTemplateAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(RestTemplateAutoConfiguration.class, HttpClientAutoConfiguration.class)); @Test void restTemplateBuilderConfigurerShouldBeLazilyDefined() { @@ -191,6 +193,18 @@ void whenReactiveWebApplicationRestTemplateBuilderIsNotConfigured() { .doesNotHaveBean(RestTemplateBuilderConfigurer.class)); } + @Test + void whenHasFactoryProperty() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withUserConfiguration(RestTemplateConfig.class) + .withPropertyValues("spring.http.client.factory=simple") + .run((context) -> { + assertThat(context).hasSingleBean(RestTemplate.class); + RestTemplate restTemplate = context.getBean(RestTemplate.class); + assertThat(restTemplate.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class); + }); + } + @Configuration(proxyBeanMethods = false) static class RestTemplateConfig { 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 eef94bb88a20..cf2970c1eccb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 java.util.concurrent.SynchronousQueue; import java.util.function.Function; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.CustomRequestLog; @@ -324,10 +325,23 @@ void customIdleTimeout() { assertThat(timeouts).containsOnly(60000L); } + @Test + void customMaxFormKeys() { + bind("server.jetty.max-form-keys=2048"); + JettyWebServer server = customizeAndGetServer(); + startAndStopToMakeInternalsAvailable(server); + List maxFormKeys = server.getServer() + .getHandlers() + .stream() + .filter(ServletContextHandler.class::isInstance) + .map(ServletContextHandler.class::cast) + .map(ServletContextHandler::getMaxFormKeys) + .toList(); + assertThat(maxFormKeys).containsOnly(2048); + } + private List connectorsIdleTimeouts(JettyWebServer server) { - // Start (and directly stop) server to have connectors available - server.start(); - server.stop(); + startAndStopToMakeInternalsAvailable(server); return Arrays.stream(server.getServer().getConnectors()) .filter((connector) -> connector instanceof AbstractConnector) .map(Connector::getIdleTimeout) @@ -344,9 +358,7 @@ private List getResponseHeaderSizes(JettyWebServer server) { private List getHeaderSizes(JettyWebServer server, Function provider) { List requestHeaderSizes = new ArrayList<>(); - // Start (and directly stop) server to have connectors available - server.start(); - server.stop(); + startAndStopToMakeInternalsAvailable(server); Connector[] connectors = server.getServer().getConnectors(); for (Connector connector : connectors) { connector.getConnectionFactories() @@ -361,6 +373,11 @@ private List getHeaderSizes(JettyWebServer server, Function getQueue(ThreadPool threadPool) { return ReflectionTestUtils.invokeMethod(threadPool, "getQueue"); } 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 f885699393ad..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 { @@ -564,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/UndertowWebServerFactoryCustomizerConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerConfigurationTests.java new file mode 100644 index 000000000000..927462ecfb75 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerConfigurationTests.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.web.embedded; + +import io.undertow.servlet.api.DeploymentInfo; +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.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration.UndertowWebServerFactoryCustomizerConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.embedded.undertow.UndertowDeploymentInfoCustomizer; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; +import org.springframework.core.task.VirtualThreadTaskExecutor; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link UndertowWebServerFactoryCustomizerConfiguration}. + * + * @author Moritz Halbritter + */ +class UndertowWebServerFactoryCustomizerConfigurationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebApplicationContext::new) + .withConfiguration(AutoConfigurations.of(EmbeddedWebServerFactoryCustomizerAutoConfiguration.class)); + + @EnabledForJreRange(min = JRE.JAVA_21) + @Test + void shouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + assertThat(context).hasSingleBean(UndertowDeploymentInfoCustomizer.class); + assertThat(context).hasBean("virtualThreadsUndertowDeploymentInfoCustomizer"); + UndertowDeploymentInfoCustomizer customizer = context.getBean(UndertowDeploymentInfoCustomizer.class); + DeploymentInfo deploymentInfo = new DeploymentInfo(); + customizer.customize(deploymentInfo); + assertThat(deploymentInfo.getExecutor()).isInstanceOf(VirtualThreadTaskExecutor.class); + }); + } + +} 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/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 53731f72fcfb..0c33a4dacd44 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 @@ -111,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; @@ -200,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); }); } @@ -601,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)); } @@ -622,12 +627,25 @@ 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", "server.reactive.session.cookie.domain:.example.com", "server.reactive.session.cookie.path:/example", "server.reactive.session.cookie.max-age:60", "server.reactive.session.cookie.http-only:false", - "server.reactive.session.cookie.secure:false", "server.reactive.session.cookie.same-site:strict") + "server.reactive.session.cookie.secure:false", "server.reactive.session.cookie.same-site:strict", + "server.reactive.session.cookie.partitioned:true") .run(assertExchangeWithSession((exchange) -> { List cookies = exchange.getResponse().getCookies().get("JSESSIONID"); assertThat(cookies).isNotEmpty(); @@ -637,6 +655,7 @@ void customSessionCookieConfigurationShouldBeApplied() { assertThat(cookies).allMatch((cookie) -> !cookie.isHttpOnly()); assertThat(cookies).allMatch((cookie) -> !cookie.isSecure()); assertThat(cookies).allMatch((cookie) -> cookie.getSameSite().equals("Strict")); + assertThat(cookies).allMatch(ResponseCookie::isPartitioned); })); } @@ -771,6 +790,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) { 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 8aec51f70eb8..838055707b7b 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 @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import jakarta.validation.Valid; @@ -597,6 +598,21 @@ void customErrorWebExceptionHandlerWithoutStatus() { }); } + @Test + void customErrorAttributesWithoutStatus() { + this.contextRunner.withUserConfiguration(CustomErrorAttributesWithoutStatus.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/"; @@ -686,6 +702,7 @@ static class CustomErrorAttributesWithoutDelegation { @Bean ErrorAttributes errorAttributes() { return new DefaultErrorAttributes() { + @Override public Map getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map errorAttributes = new HashMap<>(); @@ -724,4 +741,23 @@ protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, } + @Configuration(proxyBeanMethods = false) + static class CustomErrorAttributesWithoutStatus { + + @Bean + ErrorAttributes errorAttributes() { + return new DefaultErrorAttributes() { + + @Override + public Map getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { + Map attributes = new LinkedHashMap<>(super.getErrorAttributes(request, options)); + attributes.remove("status"); + return attributes; + } + + }; + } + + } + } 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 100d36cdd917..9bd859b7ebce 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -162,12 +162,10 @@ void dispatcherServletThrowExceptionIfNoHandlerFoundDefaultConfig() { @Test void dispatcherServletCustomConfig() { this.contextRunner - .withPropertyValues("spring.mvc.throw-exception-if-no-handler-found:false", - "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(false); assertThat(dispatcherServlet).extracting("dispatchOptionsRequest").isEqualTo(false); assertThat(dispatcherServlet).extracting("dispatchTraceRequest").isEqualTo(true); assertThat(dispatcherServlet).extracting("publishEvents").isEqualTo(false); @@ -176,15 +174,6 @@ void dispatcherServletCustomConfig() { }); } - @Test - @Deprecated(since = "3.2.0", forRemoval = true) - void dispatcherServletThrowExceptionIfNoHandlerFoundCustomConfig() { - this.contextRunner.withPropertyValues("spring.mvc.throw-exception-if-no-handler-found:false").run((context) -> { - DispatcherServlet dispatcherServlet = context.getBean(DispatcherServlet.class); - assertThat(dispatcherServlet).extracting("throwExceptionIfNoHandlerFound").isEqualTo(false); - }); - } - @Configuration(proxyBeanMethods = false) static class MultipartConfiguration { 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 c428ff17819f..73e1348c3560 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,6 +22,7 @@ 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; @@ -29,6 +30,7 @@ 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; @@ -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); @@ -161,11 +186,11 @@ void sessionStoreDir() { @Test void whenShutdownPropertyIsSetThenShutdownIsCustomized() { Map map = new HashMap<>(); - map.put("server.shutdown", "graceful"); + map.put("server.shutdown", "immediate"); bindProperties(map); ConfigurableServletWebServerFactory factory = mock(ConfigurableServletWebServerFactory.class); this.customizer.customize(factory); - then(factory).should().setShutdown(assertArg((shutdown) -> assertThat(shutdown).isEqualTo(Shutdown.GRACEFUL))); + then(factory).should().setShutdown(assertArg((shutdown) -> assertThat(shutdown).isEqualTo(Shutdown.IMMEDIATE))); } private void bindProperties(Map map) { 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 7ccc5e12bff4..474c049e3532 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,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; @@ -136,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; @@ -404,6 +406,26 @@ void customFlashMapManagerWithDifferentNameDoesNotReplaceDefaultFlashMapManager( }); } + @Test + void customViewNameTranslatorWithMatchingNameReplacesDefaultViewNameTranslator() { + this.contextRunner.withBean("viewNameTranslator", CustomViewNameTranslator.class, CustomViewNameTranslator::new) + .run((context) -> { + assertThat(context).hasSingleBean(RequestToViewNameTranslator.class); + assertThat(context.getBean("viewNameTranslator")).isInstanceOf(CustomViewNameTranslator.class); + }); + } + + @Test + void customViewNameTranslatorWithDifferentNameDoesNotReplaceDefaultViewNameTranslator() { + this.contextRunner + .withBean("customViewNameTranslator", CustomViewNameTranslator.class, CustomViewNameTranslator::new) + .run((context) -> { + assertThat(context.getBean("customViewNameTranslator")).isInstanceOf(CustomViewNameTranslator.class); + assertThat(context.getBean("viewNameTranslator")) + .isInstanceOf(DefaultRequestToViewNameTranslator.class); + }); + } + @Test void defaultDateFormat() { this.contextRunner.run((context) -> { @@ -1458,6 +1480,15 @@ protected void updateFlashMaps(List flashMaps, HttpServletRequest requ } + static class CustomViewNameTranslator implements RequestToViewNameTranslator { + + @Override + public String getViewName(HttpServletRequest requestAttributes) { + return null; + } + + } + @Configuration(proxyBeanMethods = false) static class ResourceHandlersWithChildAndParentContextConfiguration { 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/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/webservices/client/WebServiceTemplateAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfigurationTests.java index f1bd27163c51..1ca35447a09d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/webservices/client/WebServiceTemplateAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -28,6 +30,7 @@ import org.springframework.boot.webservices.client.WebServiceTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.oxm.Marshaller; import org.springframework.oxm.Unmarshaller; import org.springframework.oxm.jaxb.Jaxb2Marshaller; @@ -45,8 +48,8 @@ */ class WebServiceTemplateAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(WebServiceTemplateAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(WebServiceTemplateAutoConfiguration.class, HttpClientAutoConfiguration.class)); @Test void autoConfiguredBuilderShouldNotHaveMarshallerAndUnmarshaller() { @@ -93,6 +96,19 @@ void builderShouldBeFreshForEachUse() { .run((context) -> assertThat(context).hasNotFailed()); } + @Test + void whenHasFactoryProperty() { + this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .withPropertyValues("spring.http.client.factory=simple") + .run(assertWebServiceTemplateBuilder((builder) -> { + WebServiceTemplate webServiceTemplate = builder.build(); + assertThat(webServiceTemplate.getMessageSenders()).hasSize(1); + ClientHttpRequestMessageSender messageSender = (ClientHttpRequestMessageSender) webServiceTemplate + .getMessageSenders()[0]; + assertThat(messageSender.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class); + })); + } + private ContextConsumer assertWebServiceTemplateBuilder( Consumer builder) { return (context) -> { 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/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$TestAutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$TestAutoConfiguration.imports new file mode 100644 index 000000000000..3d625bcf4103 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$TestAutoConfiguration.imports @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$AfterDeprecatedAutoConfiguration +org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$ReplacementAutoConfiguration + diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$TestAutoConfiguration.replacements b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$TestAutoConfiguration.replacements new file mode 100644 index 000000000000..b63c6b88df3c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$TestAutoConfiguration.replacements @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$DeprecatedAutoConfiguration=\ +org.springframework.boot.autoconfigure.AutoConfigurationImportSelectorTests$ReplacementAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationReplacementsTests$TestAutoConfigurationReplacements.replacements b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationReplacementsTests$TestAutoConfigurationReplacements.replacements new file mode 100644 index 000000000000..9924a4a0c109 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationReplacementsTests$TestAutoConfigurationReplacements.replacements @@ -0,0 +1,2 @@ +com.example.A1=com.example.A2 +com.example.B1=com.example.B2 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/test/common-messages.properties b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/test/common-messages.properties new file mode 100644 index 000000000000..432ea479457a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/test/common-messages.properties @@ -0,0 +1 @@ +hello=world diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index d460dcd0ee53..6756c699185d 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -8,46 +8,31 @@ description = "Spring Boot Dependencies" bom { effectiveBomArtifact() upgrade { - policy = "same-minor-version" + policy = "same-major-version" gitHub { issueLabels = ["type: dependency-upgrade"] } } - library("ActiveMQ", "5.18.6") { + library("ActiveMQ", "6.1.3") { 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" }, - "activemq-http", - "activemq-jaas", - "activemq-jdbc-store", - "activemq-jms-pool", - "activemq-kahadb-store", - "activemq-karaf", - "activemq-log4j-appender", - "activemq-mqtt", - "activemq-openwire-generator", - "activemq-openwire-legacy", - "activemq-osgi", - "activemq-partition", - "activemq-pool", - "activemq-ra", - "activemq-run", - "activemq-runtime-config", - "activemq-shiro", "activemq-spring" { exclude group: "commons-logging", module: "commons-logging" - }, - "activemq-stomp", - "activemq-web" + } + ] + imports = [ + "activemq-bom" ] } + 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", "2.0.3") { group("org.eclipse.angus") { @@ -63,24 +48,21 @@ 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.31.2") { + library("Artemis", "2.37.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.1") { group("org.aspectj") { @@ -90,6 +72,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") { @@ -97,6 +83,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.2") { group("org.awaitility") { @@ -107,28 +97,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.2") { + 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.16.0") { + 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.4.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.19") { + library("Byte Buddy", "1.15.7") { 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") { @@ -141,6 +159,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") { @@ -151,9 +173,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.17.0") { - group("com.datastax.oss") { + library("Cassandra Driver", "4.18.1") { + group("org.apache.cassandra") { imports = [ "java-driver-bom" ] @@ -161,13 +188,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.6.0") { + 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") { @@ -175,8 +211,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.10.0") { + library("Commons DBCP2", "2.12.0") { group("org.apache.commons") { modules = [ "commons-dbcp2" { @@ -184,13 +224,19 @@ bom { } ] } + links { + site("https://commons.apache.org/proper/commons-dbcp") + } } - library("Commons Lang3", "3.13.0") { + library("Commons Lang3", "3.17.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") { @@ -205,21 +251,34 @@ bom { "commons-pool2" ] } + links { + site("https://commons.apache.org/proper/commons-pool") + } } - library("Couchbase Client", "3.4.11") { + library("Couchbase Client", "3.7.4") { group("com.couchbase.client") { modules = [ "java-client" ] } + links { + site("https://docs.couchbase.com/java-sdk/current/hello-world/overview.html") + } } - library("Crac", "1.4.0") { + library("Crac", "1.5.0") { group("org.crac") { modules = [ "crac" ] } } + library("CycloneDX Maven Plugin", "2.9.0") { + group("org.cyclonedx") { + plugins = [ + "cyclonedx-maven-plugin" + ] + } + } library("DB2 JDBC", "11.5.9.0") { group("com.ibm.db2") { modules = [ @@ -233,8 +292,17 @@ bom { "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", @@ -246,13 +314,6 @@ bom { ] } } - library("Dropwizard Metrics", "4.2.28") { - group("io.dropwizard.metrics") { - imports = [ - "metrics-bom" - ] - } - } library("Ehcache3", "3.10.8") { group("org.ehcache") { modules = [ @@ -267,8 +328,12 @@ bom { } ] } + links { + site("https://www.ehcache.org/") + releaseNotes("https://github.com/ehcache/ehcache3/releases/tag/v{version}") + } } - library("Elasticsearch Client", "8.10.4") { + library("Elasticsearch Client", "8.15.3") { group("org.elasticsearch.client") { modules = [ "elasticsearch-rest-client" { @@ -284,20 +349,42 @@ bom { "elasticsearch-java" ] } + links { + releaseNotes("https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-{version}.html") + } } - library("Flyway", "9.22.3") { + library("Flyway", "10.20.0") { group("org.flywaydb") { modules = [ + "flyway-commandline", "flyway-core", + "flyway-database-cassandra", + "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") + javadoc("https://javadoc.io/doc/org.flywaydb/flyway-core/{version}") + } } library("FreeMarker", "2.3.33") { group("org.freemarker") { @@ -305,13 +392,22 @@ bom { "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", "6.0.0") { + 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") { @@ -327,7 +423,7 @@ bom { ] } } - library("GraphQL Java", "21.5") { + library("GraphQL Java", "22.3") { prohibit { startsWith(["2018-", "2019-", "2020-", "2021-", "230521-"]) because "These are snapshots that we don't want to see" @@ -342,6 +438,11 @@ bom { "graphql-java" ] } + links { + site("https://www.graphql-java.com/") + javadoc("https://javadoc.io/doc/com.graphql-java/graphql-java/{version}") + releaseNotes("https://github.com/graphql-java/graphql-java/releases/tag/v{version}") + } } library("Groovy", "4.0.23") { group("org.apache.groovy") { @@ -349,20 +450,32 @@ bom { "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") + javadoc("https://javadoc.io/doc/com.google.code.gson/gson/{version}") + releaseNotes("https://github.com/google/gson/releases/tag/gson-parent-{version}") + } } - library("H2", "2.2.224") { + library("H2", "2.3.232") { 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") { @@ -373,15 +486,20 @@ bom { ] } } - library("Hazelcast", "5.3.8") { + library("Hazelcast", "5.5.0") { group("com.hazelcast") { modules = [ "hazelcast", "hazelcast-spring" ] } + links { + site("https://hazelcast.com") + javadoc("https://javadoc.io/doc/com.hazelcast/hazelcast/{version}") + releaseNotes("https://github.com/hazelcast/hazelcast/releases/tag/v{version}") + } } - library("Hibernate", "6.4.10.Final") { + library("Hibernate", "6.6.1.Final") { group("org.hibernate.orm") { modules = [ "hibernate-agroal", @@ -401,6 +519,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") { @@ -410,7 +539,7 @@ bom { ] } } - library("HikariCP", "5.0.1") { + library("HikariCP", "5.1.0") { group("com.zaxxer") { modules = [ "HikariCP" @@ -424,14 +553,18 @@ bom { ] } } - library("HtmlUnit", "2.70.0") { - group("net.sourceforge.htmlunit") { + library("HtmlUnit", "4.5.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") { @@ -442,13 +575,12 @@ bom { ] } } - library("HttpClient5", "5.2.3") { + library("HttpClient5", "5.4") { group("org.apache.httpcomponents.client5") { modules = [ "httpclient5", "httpclient5-cache", - "httpclient5-fluent", - "httpclient5-win", + "httpclient5-fluent" ] } } @@ -460,7 +592,7 @@ bom { ] } } - library("HttpCore5", "5.2.5") { + library("HttpCore5", "5.3.1") { group("org.apache.httpcomponents.core5") { modules = [ "httpcore5", @@ -469,19 +601,27 @@ bom { ] } } - library("Infinispan", "14.0.32.Final") { + library("Infinispan", "15.0.10.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") { @@ -496,6 +636,12 @@ bom { "jakarta.activation-api" ] } + links { + site("https://github.com/jakartaee/jaf-api") + javadoc { version -> "https://jakarta.ee/specifications/activation/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + releaseNotes("https://github.com/jakartaee/jaf-api/releases/tag/{version}") + } } library("Jakarta Annotation", "2.1.1") { group("jakarta.annotation") { @@ -503,6 +649,21 @@ bom { "jakarta.annotation-api" ] } + links { + javadoc { version -> "https://jakarta.ee/specifications/annotations/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } + } + library("Jakarta Inject", "2.0.1") { + group("jakarta.inject") { + modules = [ + "jakarta.inject-api" + ] + } + links { + javadoc { version -> "https://jakarta.ee/specifications/dependency-injection/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } library("Jakarta JMS", "3.1.0") { group("jakarta.jms") { @@ -510,6 +671,12 @@ bom { "jakarta.jms-api" ] } + links { + site { version -> "https://jakarta.ee/specifications/messaging/%s.%s" + .formatted(version.major(), version.minor()) } + javadoc { version -> "https://jakarta.ee/specifications/messaging/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } library("Jakarta Json", "2.1.3") { group("jakarta.json") { @@ -517,6 +684,10 @@ bom { "jakarta.json-api" ] } + links { + javadoc { version -> "https://jakarta.ee/specifications/jsonp/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } library("Jakarta Json Bind", "3.0.1") { group("jakarta.json.bind") { @@ -524,6 +695,10 @@ bom { "jakarta.json.bind-api" ] } + links { + javadoc { version -> "https://jakarta.ee/specifications/jsonb/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } library("Jakarta Mail", "2.1.3") { group("jakarta.mail") { @@ -531,6 +706,13 @@ bom { "jakarta.mail-api" ] } + links { + site { version -> "https://jakarta.ee/specifications/mail/%s.%s" + .formatted(version.major(), version.minor()) } + javadoc { version -> "https://jakarta.ee/specifications/mail/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + releaseNotes("https://github.com/jakartaee/mail-api/releases/tag/{version}") + } } library("Jakarta Management", "1.1.4") { group("jakarta.management.j2ee") { @@ -540,18 +722,38 @@ 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" ] } + links { + site { version -> "https://jakarta.ee/specifications/persistence/%s.%s" + .formatted(version.major(), version.minor()) } + javadoc { version -> "https://jakarta.ee/specifications/persistence/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } 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" ] } + links { + site { version -> "https://jakarta.ee/specifications/servlet/%s.%s" + .formatted(version.major(), version.minor()) } + javadoc { version -> "https://jakarta.ee/specifications/servlet/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } library("Jakarta Servlet JSP JSTL", "3.0.2") { group("jakarta.servlet.jsp.jstl") { @@ -566,15 +768,31 @@ bom { "jakarta.transaction-api" ] } + links { + javadoc { version -> "https://jakarta.ee/specifications/transactions/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } 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" ] } + links { + javadoc { version -> "https://jakarta.ee/specifications/bean-validation/%s.%s/apidocs" + .formatted(version.major(), version.minor()) } + } } 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", @@ -651,7 +869,7 @@ bom { ] } } - library("JBoss Logging", "3.5.3.Final") { + library("JBoss Logging", "3.6.1.Final") { group("org.jboss.logging") { modules = [ "jboss-logging" @@ -665,12 +883,16 @@ bom { ] } } - library("Jedis", "5.0.2") { + library("Jedis", "5.2.0") { 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.9") { group("org.glassfish.jersey") { @@ -678,6 +900,10 @@ bom { "jersey-bom" ] } + links { + site("https://github.com/eclipse-ee4j/jersey") + releaseNotes("https://github.com/eclipse-ee4j/jersey/releases/tag/{version}") + } } library("Jetty Reactive HTTPClient", "4.0.8") { group("org.eclipse.jetty") { @@ -697,15 +923,19 @@ bom { "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.21") { + library("jOOQ", "3.19.14") { group("org.jooq") { modules = [ "jooq", @@ -717,6 +947,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") { @@ -725,6 +960,10 @@ 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.5.1") { group("net.minidev") { @@ -732,6 +971,10 @@ bom { "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.3") { group("org.skyscreamer") { @@ -739,6 +982,10 @@ bom { "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") { @@ -760,8 +1007,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.6.2") { + library("Kafka", "3.8.0") { group("org.apache.kafka") { modules = [ "connect", @@ -804,6 +1057,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") { @@ -814,29 +1071,55 @@ 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.7.3") { + library("Kotlin Coroutines", "1.8.1") { + prohibit { + versionRange "[1.9.0,)" + because "it requires Kotlin 2" + } 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") { + prohibit { + versionRange "[1.7.0-RC,)" + because "it requires Kotlin 2" + } 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.3.2.RELEASE") { + library("Lettuce", "6.4.0.RELEASE") { group("io.lettuce") { modules = [ "lettuce-core" ] } + 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.24.0") { + library("Liquibase", "4.29.2") { group("org.liquibase") { modules = [ "liquibase-cdi", @@ -846,22 +1129,33 @@ bom { "liquibase-maven-plugin" ] } + links { + site("https://www.liquibase.com") + releaseNotes("https://github.com/liquibase/liquibase/releases/tag/v{version}") + } } - library("Log4j2", "2.21.1") { + library("Log4j2", "2.24.1") { group("org.apache.logging.log4j") { imports = [ "log4j-bom" ] } + links { + site("https://logging.apache.org/log4j") + docs { version -> "https://logging.apache.org/log4j/%s.x/manual".formatted(version.major()) } + releaseNotes("https://github.com/apache/logging-log4j2/releases/tag/rel%2F{version}") + } } - library("Logback", "1.4.14") { + library("Logback", "1.5.11") { group("ch.qos.logback") { modules = [ - "logback-access", "logback-classic", "logback-core" ] } + links { + site("https://logback.qos.ch") + } } library("Lombok", "1.18.34") { group("org.projectlombok") { @@ -869,13 +1163,21 @@ bom { "lombok" ] } + links { + site("https://projectlombok.org") + } } - library("MariaDB", "3.3.3") { + library("MariaDB", "3.4.1") { 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") { @@ -884,28 +1186,28 @@ bom { ] } } - library("Maven Assembly Plugin", "3.6.0") { + library("Maven Assembly Plugin", "3.7.1") { group("org.apache.maven.plugins") { plugins = [ "maven-assembly-plugin" ] } } - library("Maven Clean Plugin", "3.3.2") { + 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.6.1") { + library("Maven Dependency Plugin", "3.8.0") { group("org.apache.maven.plugins") { plugins = [ "maven-dependency-plugin" @@ -919,21 +1221,21 @@ bom { ] } } - library("Maven Enforcer Plugin", "3.4.1") { + library("Maven Enforcer Plugin", "3.5.0") { group("org.apache.maven.plugins") { plugins = [ "maven-enforcer-plugin" ] } } - library("Maven Failsafe Plugin", "3.1.2") { + library("Maven Failsafe Plugin", "3.5.1") { group("org.apache.maven.plugins") { plugins = [ "maven-failsafe-plugin" ] } } - library("Maven Help Plugin", "3.4.1") { + library("Maven Help Plugin", "3.5.1") { group("org.apache.maven.plugins") { plugins = [ "maven-help-plugin" @@ -947,21 +1249,21 @@ bom { ] } } - library("Maven Invoker Plugin", "3.6.1") { + library("Maven Invoker Plugin", "3.8.1") { 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.6.3") { + library("Maven Javadoc Plugin", "3.10.1") { group("org.apache.maven.plugins") { plugins = [ "maven-javadoc-plugin" @@ -975,7 +1277,7 @@ bom { ] } } - library("Maven Shade Plugin", "3.5.3") { + library("Maven Shade Plugin", "3.6.0") { group("org.apache.maven.plugins") { plugins = [ "maven-shade-plugin" @@ -989,7 +1291,7 @@ bom { ] } } - library("Maven Surefire Plugin", "3.1.2") { + library("Maven Surefire Plugin", "3.5.1") { group("org.apache.maven.plugins") { plugins = [ "maven-surefire-plugin" @@ -1003,7 +1305,7 @@ bom { ] } } - library("Micrometer", "1.12.11") { + library("Micrometer", "1.14.0-RC1") { considerSnapshots() group("io.micrometer") { modules = [ @@ -1015,14 +1317,28 @@ bom { "micrometer-bom" ] } + links { + site("https://micrometer.io") + javadoc("https://javadoc.io/doc/io.micrometer/micrometer-core/{version}") + 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.2.11") { + library("Micrometer Tracing", "1.4.0-RC1") { considerSnapshots() group("io.micrometer") { imports = [ "micrometer-tracing-bom" ] } + links { + site("https://micrometer.io") + javadoc("https://javadoc.io/doc/io.micrometer/micrometer-tracing/{version}") + 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", "${mockitoVersion}") { group("org.mockito") { @@ -1030,8 +1346,12 @@ bom { "mockito-bom" ] } + links { + site("https://site.mockito.org/") + releaseNotes("https://github.com/mockito/mockito/releases/tag/v{version}") + } } - library("MongoDB", "4.11.4") { + library("MongoDB", "5.2.0") { group("org.mongodb") { modules = [ "bson", @@ -1042,19 +1362,32 @@ 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", "12.4.2.jre11") { + library("MSSQL JDBC", "12.8.1.jre11") { + prohibit { + endsWith(".jre8") + because "we want to use the jre11 version" + } prohibit { - endsWith([".jre8", "-preview"]) - because "we use the non-preview .jre11 version" + endsWith("-preview") + because "we only want to use non-preview releases" } 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.3.0") { + library("MySQL", "9.1.0") { group("com.mysql") { modules = [ "mysql-connector-j" { @@ -1069,6 +1402,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") { @@ -1089,6 +1426,10 @@ 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.114.Final") { group("io.netty") { @@ -1096,22 +1437,23 @@ bom { "netty-bom" ] } - } - library("OkHttp", "4.12.0") { - group("com.squareup.okhttp3") { - imports = [ - "okhttp-bom" - ] + links { + site("https://netty.io") } } - library("OpenTelemetry", "1.31.0") { + library("OpenTelemetry", "1.43.0") { group("io.opentelemetry") { imports = [ "opentelemetry-bom" ] } + links { + site("https://github.com/open-telemetry/opentelemetry-java") + javadoc("https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-common/{version}") + releaseNotes("https://github.com/open-telemetry/opentelemetry-java/releases/tag/v{version}") + } } - library("Oracle Database", "21.9.0.0") { + library("Oracle Database", "23.5.0.24.07") { alignWith { dependencyManagementDeclaredIn("com.oracle.database.jdbc:ojdbc-bom") } @@ -1121,18 +1463,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", @@ -1149,20 +1479,9 @@ 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") { @@ -1172,7 +1491,7 @@ bom { ] } } - library("Oracle R2DBC", "1.1.1") { + library("Oracle R2DBC", "1.2.0") { group("com.oracle.database.r2dbc") { modules = [ "oracle-r2dbc" @@ -1186,98 +1505,51 @@ bom { ] } } - library("Postgresql", "42.6.2") { + library("Postgresql", "42.7.4") { 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", "1.3.2") { + 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 Client", "0.16.0") { + 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.1.3") { + library("Pulsar", "3.3.2") { group("org.apache.pulsar") { - modules = [ - "bouncy-castle-bc", - "bouncy-castle-bcfips", - "pulsar-client-1x-base", - "pulsar-client-1x", - "pulsar-client-2x-shaded", - "pulsar-client-admin-api", - "pulsar-client-admin-original", - "pulsar-client-admin", - "pulsar-client-all", - "pulsar-client-api", - "pulsar-client-auth-athenz", - "pulsar-client-auth-sasl", - "pulsar-client-messagecrypto-bc", - "pulsar-client-original", - "pulsar-client-tools-api", - "pulsar-client-tools", - "pulsar-client", - "pulsar-common", - "pulsar-config-validation", - "pulsar-functions-api", - "pulsar-functions-proto", - "pulsar-functions-utils", - "pulsar-io-aerospike", - "pulsar-io-alluxio", - "pulsar-io-aws", - "pulsar-io-batch-data-generator", - "pulsar-io-batch-discovery-triggerers", - "pulsar-io-canal", - "pulsar-io-cassandra", - "pulsar-io-common", - "pulsar-io-core", - "pulsar-io-data-generator", - "pulsar-io-debezium-core", - "pulsar-io-debezium-mongodb", - "pulsar-io-debezium-mssql", - "pulsar-io-debezium-mysql", - "pulsar-io-debezium-oracle", - "pulsar-io-debezium-postgres", - "pulsar-io-debezium", - "pulsar-io-dynamodb", - "pulsar-io-elastic-search", - "pulsar-io-file", - "pulsar-io-flume", - "pulsar-io-hbase", - "pulsar-io-hdfs2", - "pulsar-io-hdfs3", - "pulsar-io-http", - "pulsar-io-influxdb", - "pulsar-io-jdbc-clickhouse", - "pulsar-io-jdbc-core", - "pulsar-io-jdbc-mariadb", - "pulsar-io-jdbc-openmldb", - "pulsar-io-jdbc-postgres", - "pulsar-io-jdbc-sqlite", - "pulsar-io-jdbc", - "pulsar-io-kafka-connect-adaptor-nar", - "pulsar-io-kafka-connect-adaptor", - "pulsar-io-kafka", - "pulsar-io-kinesis", - "pulsar-io-mongo", - "pulsar-io-netty", - "pulsar-io-nsq", - "pulsar-io-rabbitmq", - "pulsar-io-redis", - "pulsar-io-solr", - "pulsar-io-twitter", - "pulsar-io", - "pulsar-metadata", - "pulsar-presto-connector-original", - "pulsar-presto-connector", - "pulsar-sql", - "pulsar-transaction-common", - "pulsar-websocket" + 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.8") { group("org.apache.pulsar") { @@ -1289,6 +1561,10 @@ bom { "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") { @@ -1300,13 +1576,22 @@ bom { "quartz-jobs" ] } + links { + site("https://github.com/quartz-scheduler/quartz") + javadoc("https://www.javadoc.io/doc/org.quartz-scheduler/quartz/{version}") + } } - 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() @@ -1316,7 +1601,7 @@ bom { ] } } - library("R2DBC MariaDB", "1.1.4") { + library("R2DBC MariaDB", "1.2.2") { group("org.mariadb") { modules = [ "r2dbc-mariadb" @@ -1330,7 +1615,7 @@ bom { ] } } - library("R2DBC MySQL", "1.0.6") { + library("R2DBC MySQL", "1.3.0") { group("io.asyncer") { modules = [ "r2dbc-mysql" @@ -1344,6 +1629,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.7.RELEASE") { considerSnapshots() @@ -1368,20 +1657,33 @@ bom { "r2dbc-spi" ] } + links { + site("https://r2dbc.io") + javadoc("https://r2dbc.io/spec/{version}/api") + } } - library("Rabbit AMQP Client", "5.19.0") { + library("Rabbit AMQP Client", "5.22.0") { group("com.rabbitmq") { modules = [ "amqp-client" ] } + links { + site("https://github.com/rabbitmq/rabbitmq-java-client") + javadoc("https://rabbitmq.github.io/rabbitmq-java-client/api/current/") + releaseNotes("https://github.com/rabbitmq/rabbitmq-java-client/releases/tag/v{version}") + } } - library("Rabbit Stream Client", "0.14.0") { + library("Rabbit Stream Client", "0.18.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") { @@ -1390,7 +1692,7 @@ bom { ] } } - library("Reactor Bom", "2023.0.11") { + library("Reactor Bom", "2024.0.0-RC1") { considerSnapshots() calendarName = "Reactor" group("io.projectreactor") { @@ -1398,8 +1700,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" @@ -1416,6 +1722,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.9") { group("io.reactivex.rxjava3") { @@ -1440,7 +1750,7 @@ 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", @@ -1505,6 +1815,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/{version}/api/java") + docs("https://docs.spring.io/spring-boot/{version}") + 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") { @@ -1513,26 +1832,38 @@ bom { ] } } - library("Selenium", "4.14.1") { + library("Selenium", "4.25.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.13.0") { + library("Selenium HtmlUnit", "4.25.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.3") { 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.16") { group("org.slf4j") { @@ -1551,38 +1882,65 @@ bom { ] } } - library("SnakeYAML", "2.2") { + library("SnakeYAML", "${snakeYamlVersion}") { group("org.yaml") { modules = [ "snakeyaml" ] } } - library("Spring AMQP", "3.1.7") { + library("Spring AMQP", "3.2.0-RC1") { 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 { version -> "https://docs.spring.io/spring-amqp/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-amqp/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-amqp/releases/tag/v{version}") + } } - library("Spring Authorization Server", "1.2.7") { + library("Spring Authorization Server", "1.4.0-RC1") { 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 {version -> "https://docs.spring.io/spring-authorization-server/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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.1.2") { + library("Spring Batch", "5.2.0-RC1") { 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 { version -> "https://docs.spring.io/spring-batch/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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.1.11") { + library("Spring Data Bom", "2024.1.0-RC1") { considerSnapshots() calendarName = "Spring Data Release" group("org.springframework.data") { @@ -1590,7 +1948,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() @@ -1599,8 +1961,17 @@ bom { "spring-framework-bom" ] } + links { + site("https://spring.io/projects/spring-framework") + github("https://github.com/spring-projects/spring-framework") + javadoc { version ->"https://docs.spring.io/spring-framework/docs/%s/javadoc-api" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-framework/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-framework/releases/tag/v{version}") + } } - library("Spring GraphQL", "1.2.9") { + library("Spring GraphQL", "1.3.3") { considerSnapshots() group("org.springframework.graphql") { modules = [ @@ -1608,24 +1979,51 @@ bom { "spring-graphql-test" ] } + links { + site("https://spring.io/projects/spring-graphql") + github("https://github.com/spring-projects/spring-graphql") + javadoc { version -> "https://docs.spring.io/spring-graphql/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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.2.5") { + library("Spring HATEOAS", "2.4.0-RC2") { 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 { version -> "https://docs.spring.io/spring-hateoas/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-hateoas/docs/%s/reference/html" + .formatted(version.forMajorMinorGeneration()) } + releaseNotes("https://github.com/spring-projects/spring-hateoas/releases/tag/{version}") + } } - library("Spring Integration", "6.2.10") { + library("Spring Integration", "6.4.0-RC1") { 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 { version -> "https://docs.spring.io/spring-integration/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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.1.9") { + library("Spring Kafka", "3.3.0-RC1") { considerSnapshots() group("org.springframework.kafka") { modules = [ @@ -1633,6 +2031,15 @@ bom { "spring-kafka-test" ] } + links { + site("https://spring.io/projects/spring-kafka") + github("https://github.com/spring-projects/spring-kafka") + javadoc { version -> "https://docs.spring.io/spring-kafka/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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.2.7") { considerSnapshots() @@ -1644,14 +2051,32 @@ bom { "spring-ldap-test" ] } + links { + site("https://spring.io/projects/spring-ldap") + github("https://github.com/spring-projects/spring-ldap") + javadoc { version -> "https://docs.spring.io/spring-ldap/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-ldap/reference/%s" + .formatted(version.forAntora()) } + releaseNotes("https://github.com/spring-projects/spring-ldap/releases/tag/{version}") + } } - library("Spring Pulsar", "1.0.11") { + library("Spring Pulsar", "1.2.0-RC1") { 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 { version -> "https://docs.spring.io/spring-pulsar/docs/%s/api/" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-pulsar/docs/%s/reference" + .formatted(version.forMajorMinorGeneration()) } + releaseNotes("https://github.com/spring-projects/spring-pulsar/releases/tag/v{version}") + } } library("Spring RESTDocs", "3.0.2") { considerSnapshots() @@ -1660,6 +2085,15 @@ bom { "spring-restdocs-bom" ] } + links { + site("https://spring.io/projects/spring-restdocs") + github("https://github.com/spring-projects/spring-restdocs") + javadoc { version -> "https://docs.spring.io/spring-restdocs/docs/%s/api/" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-restdocs/docs/%s/reference/htmlsingle/" + .formatted(version.forMajorMinorGeneration()) } + releaseNotes("https://github.com/spring-projects/spring-restdocs/releases/tag/v{version}") + } } library("Spring Retry", "2.0.10") { considerSnapshots() @@ -1668,16 +2102,29 @@ bom { "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.2.7") { + library("Spring Security", "6.4.0-RC1") { 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 { version -> "https://docs.spring.io/spring-security/site/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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.2.6") { + library("Spring Session", "3.4.0-RC1") { considerSnapshots() prohibit { startsWith(["Apple-", "Bean-", "Corn-", "Dragonfruit-"]) @@ -1688,6 +2135,15 @@ bom { "spring-session-bom" ] } + links { + site("https://spring.io/projects/spring-session") + github("https://github.com/spring-projects/spring-session") + javadoc { version -> "https://docs.spring.io/spring-session/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + 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() @@ -1696,20 +2152,48 @@ bom { "spring-ws-bom" ] } + links("spring-webservices") { + site("https://spring.io/projects/spring-ws") + github("https://github.com/spring-projects/spring-ws") + javadoc { version -> "https://docs.spring.io/spring-ws/docs/%s/api" + .formatted(version.forMajorMinorGeneration()) } + docs { version -> "https://docs.spring.io/spring-ws/docs/%s/reference/html" + .formatted(version.forMajorMinorGeneration()) } + releaseNotes("https://github.com/spring-projects/spring-ws/releases/tag/v{version}") + } } - library("SQLite JDBC", "3.43.2.2") { + library("SQLite JDBC", "3.47.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.19.8") { + library("Testcontainers", "1.20.3") { group("org.testcontainers") { imports = [ "testcontainers-bom" ] } + links { + site("https://java.testcontainers.org") + javadoc("https://javadoc.io/doc/org.testcontainers/testcontainers/{version}") + releaseNotes("https://github.com/testcontainers/testcontainers-java/releases/tag/{version}") + } + } + library("Testcontainers Redis Module", "2.2.2") { + group("com.redis") { + modules = [ + "testcontainers-redis" + ] + } + links { + site("https://testcontainers.com/modules/redis/") + } } library("Thymeleaf", "3.1.2.RELEASE") { group("org.thymeleaf") { @@ -1718,6 +2202,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") { @@ -1756,6 +2244,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") { @@ -1777,14 +2269,21 @@ bom { ] } } - library("Versions Maven Plugin", "2.16.2") { + library("Versions Maven Plugin", "2.17.1") { group("org.codehaus.mojo") { plugins = [ "versions-maven-plugin" ] } } - library("WebJars Locator Core", "0.55") { + 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" @@ -1805,7 +2304,7 @@ bom { ] } } - library("XmlUnit2", "2.9.1") { + library("XmlUnit2", "2.10.0") { group("org.xmlunit") { modules = [ "xmlunit-assertj", @@ -1817,6 +2316,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.4") { group("org.eclipse") { @@ -1824,6 +2327,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/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/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/restart/MainMethodTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/MainMethodTests.java index b8fce3b1a317..758c3f95c7ef 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 @@ -66,7 +66,7 @@ void nestedMainMethod() throws Exception { } @Test // gh-39733 - void vaiJarLauncher() throws Exception { + void viaJarLauncher() throws Exception { FakeJarLauncher.action = (args) -> Valid.main(args); MainMethod method = new TestThread(FakeJarLauncher::main).test(); Method expectedMain = Valid.class.getMethod("main", String[].class); diff --git a/spring-boot-project/spring-boot-docker-compose/build.gradle b/spring-boot-project/spring-boot-docker-compose/build.gradle index be484f8b8dc8..73c4c32df884 100644 --- a/spring-boot-project/spring-boot-docker-compose/build.gradle +++ b/spring-boot-project/spring-boot-docker-compose/build.gradle @@ -12,20 +12,27 @@ dependencies { api(project(":spring-boot-project:spring-boot")) dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("com.hazelcast:hazelcast") + dockerTestImplementation("com.redis:testcontainers-redis") dockerTestImplementation("org.assertj:assertj-core") dockerTestImplementation("org.awaitility:awaitility") dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestImplementation("org.testcontainers:testcontainers") + dockerTestRuntimeOnly("com.clickhouse:clickhouse-jdbc") + dockerTestRuntimeOnly("com.clickhouse:clickhouse-r2dbc") 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") optional(project(":spring-boot-project:spring-boot-autoconfigure")) optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) + optional("com.hazelcast:hazelcast") optional("io.r2dbc:r2dbc-spi") optional("org.mongodb:mongodb-driver-core") optional("org.neo4j.driver:neo4j-java-driver") diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/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 index 409793e1930b..0ad06a64c6a2 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/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 @@ -24,10 +24,13 @@ import java.time.Duration; import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.docker.compose.core.DockerCli.DockerComposeOptions; import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeConfig; import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeDown; import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposePs; @@ -60,15 +63,17 @@ class DockerCliIntegrationTests { @Test void runBasicCommand() { - DockerCli cli = new DockerCli(null, null, Collections.emptySet()); + DockerCli cli = new DockerCli(null, null); List context = cli.run(new DockerCliCommand.Context()); assertThat(context).isNotEmpty(); } @Test void runLifecycle() throws IOException { - File composeFile = createComposeFile(); - DockerCli cli = new DockerCli(null, DockerComposeFile.of(composeFile), Collections.emptySet()); + File composeFile = createComposeFile("redis-compose.yaml"); + String projectName = UUID.randomUUID().toString(); + DockerCli cli = new DockerCli(null, new DockerComposeOptions(DockerComposeFile.of(composeFile), + Collections.emptySet(), List.of("--project-name=" + projectName))); try { // Verify that no services are running (this is a fresh compose project) List ps = cli.run(new ComposePs()); @@ -76,8 +81,9 @@ void runLifecycle() throws IOException { // List the config and verify that redis is there DockerCliComposeConfigResponse config = cli.run(new ComposeConfig()); assertThat(config.services()).containsOnlyKeys("redis"); + assertThat(config.name()).isEqualTo(projectName); // 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 +92,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(); } @@ -103,22 +109,52 @@ void runLifecycle() throws IOException { } } + @Test + void shouldWorkWithMultipleComposeFiles() throws IOException { + List composeFiles = createComposeFiles(); + DockerCli cli = new DockerCli(null, + new DockerComposeOptions(DockerComposeFile.of(composeFiles), Set.of("dev"), Collections.emptyList())); + try { + // List the config and verify that both redis are there + DockerCliComposeConfigResponse config = cli.run(new ComposeConfig()); + assertThat(config.services()).containsOnlyKeys("redis1", "redis2"); + // Run up + cli.run(new ComposeUp(LogLevel.INFO, Collections.emptyList())); + // Run ps and use id to run inspect on the id + List ps = cli.run(new ComposePs()); + assertThat(ps).hasSize(2); + } + finally { + // Clean up in any case + quietComposeDown(cli); + } + } + 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 } } - 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}", TestImage.REDIS.toString()); - FileCopyUtils.copy(composeFileContent, new FileWriter(tempComposeFile)); - return tempComposeFile; + private static File createComposeFile(String resource) throws IOException { + File source = new ClassPathResource(resource, DockerCliIntegrationTests.class).getFile(); + File target = Path.of(tempDir.toString(), source.getName()).toFile(); + String content = FileCopyUtils.copyToString(new FileReader(source)); + content = content.replace("{imageName}", TestImage.REDIS.toString()); + try (FileWriter writer = new FileWriter(target)) { + FileCopyUtils.copy(content, writer); + } + return target; + } + + private static List createComposeFiles() throws IOException { + File file1 = createComposeFile("1.yaml"); + File file2 = createComposeFile("2.yaml"); + File file3 = createComposeFile("3.yaml"); + return List.of(file1, file2, file3); } } 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/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 index d51a5f39a9fa..90111fc51f5a 100644 --- 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 @@ -34,6 +34,15 @@ 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); @@ -41,7 +50,7 @@ void runCreatesConnectionDetails(CassandraConnectionDetails connectionDetails) { assertThat(node.port()).isGreaterThan(0); assertThat(connectionDetails.getUsername()).isNull(); assertThat(connectionDetails.getPassword()).isNull(); - assertThat(connectionDetails.getLocalDatacenter()).isEqualTo("dc1"); + 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/clickhouse/ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..c7594e890105 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.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.clickhouse; + +import java.sql.Driver; + +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 ClickHouseJdbcDockerComposeConnectionDetailsFactory}. + * + * @author Stephane Nicoll + */ +class ClickHouseJdbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE) + void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException { + assertConnectionDetails(connectionDetails); + checkDatabaseAccess(connectionDetails); + } + + @DockerComposeTest(composeFile = "clickhouse-bitnami-compose.yaml", image = TestImage.BITNAMI_CLICKHOUSE) + void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + // See https://github.com/bitnami/containers/issues/73550 + // checkDatabaseAccess(connectionDetails); + } + + private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:clickhouse://").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.CLICKHOUSE.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/clickhouse/ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..939043ee5945 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests.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.docker.compose.service.connection.clickhouse; + +import java.time.Duration; + +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import reactor.core.publisher.Mono; + +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 static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ClickHouseR2dbcDockerComposeConnectionDetailsFactory}. + * + * @author Stephane Nicoll + */ +class ClickHouseR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "clickhouse-compose.yaml", image = TestImage.CLICKHOUSE) + void runCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + checkDatabaseAccess(connectionDetails); + } + + @DockerComposeTest(composeFile = "clickhouse-bitnami-compose.yaml", image = TestImage.BITNAMI_CLICKHOUSE) + void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + // See https://github.com/bitnami/containers/issues/73550 + // checkDatabaseAccess(connectionDetails); + } + + private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + assertThat(connectionFactoryOptions.toString()).contains("database=mydatabase", "driver=clickhouse", + "password=REDACTED", "user=myuser"); + assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret"); + } + + private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) { + ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions(); + ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); + String sql = DatabaseDriver.CLICKHOUSE.getValidationQuery(); + Integer result = Mono.from(connectionFactory.create()) + .flatMapMany((connection) -> connection.createStatement(sql).execute()) + .flatMap((r) -> r.map((row, rowMetadata) -> row.get(0, Integer.class))) + .blockFirst(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/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/ElasticsearchDockerComposeConnectionDetailsFactoryIntegrationTests.java index d49c8d2069b1..48f1c0d6b6d4 100644 --- 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 @@ -30,11 +30,21 @@ * @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(); diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..189f079edc26 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.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.hazelcast; + +import java.util.UUID; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.config.Config; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; + +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +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 HazelcastDockerComposeConnectionDetailsFactory}. + * + * @author Dmytro Nosan + */ +class HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "hazelcast-compose.yaml", image = TestImage.HAZELCAST) + void runCreatesConnectionDetails(HazelcastConnectionDetails connectionDetails) { + ClientConfig config = connectionDetails.getClientConfig(); + assertThat(config.getClusterName()).isEqualTo(Config.DEFAULT_CLUSTER_NAME); + verifyConnection(config); + } + + @DockerComposeTest(composeFile = "hazelcast-cluster-name-compose.yaml", image = TestImage.HAZELCAST) + void runCreatesConnectionDetailsCustomClusterName(HazelcastConnectionDetails connectionDetails) { + ClientConfig config = connectionDetails.getClientConfig(); + assertThat(config.getClusterName()).isEqualTo("spring-boot"); + verifyConnection(config); + } + + private static void verifyConnection(ClientConfig config) { + HazelcastInstance hazelcastInstance = HazelcastClient.newHazelcastClient(config); + try { + IMap map = hazelcastInstance.getMap(UUID.randomUUID().toString()); + map.put("docker", "compose"); + assertThat(map.get("docker")).isEqualTo("compose"); + } + finally { + hazelcastInstance.shutdown(); + } + } + +} 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/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/mariadb/MariaDbJdbcDockerComposeConnectionDetailsFactoryIntegrationTests.java index 289f7d9dcbac..70425eb30e77 100644 --- 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 @@ -28,11 +28,21 @@ * @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 index fc1a29a61015..5e1c85488cb0 100644 --- 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 @@ -30,11 +30,21 @@ * @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"); 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 index 833df4430031..928637f6dfd5 100644 --- 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 @@ -17,10 +17,12 @@ 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; @@ -36,11 +38,21 @@ 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("mydatabase"); + 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 index b88b06b5d7a5..fb8e3aa51382 100644 --- 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 @@ -28,11 +28,21 @@ * @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 index 9c4db72615c4..536d2759baf1 100644 --- 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 @@ -35,6 +35,15 @@ 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"); 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 index bbeaf95d7ccc..6e86f9f22d8f 100644 --- 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 @@ -31,12 +31,22 @@ * 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) { - assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("neo4j", "secret")); + 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/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..022d1d155525 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.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.otlp; + +import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +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 OpenTelemetryLoggingDockerComposeConnectionDetailsFactory} + * using {@link TestImage#GRAFANA_OTEL_LGTM}. + * + * @author Eddú Meléndez + */ +class GrafanaOpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.GRAFANA_OTEL_LGTM) + void runCreatesConnectionDetails(OtlpLoggingConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUrl(Transport.HTTP)).startsWith("http://").endsWith("/v1/logs"); + assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..81f9632a3923 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests.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} + * using {@link TestImage#GRAFANA_OTEL_LGTM}. + * + * @author Eddú Meléndez + */ +class GrafanaOpenTelemetryMetricsDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.GRAFANA_OTEL_LGTM) + 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/GrafanaOpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..bc43d4112d05 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/GrafanaOpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests.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.otlp; + +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; +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} + * using {@link TestImage#GRAFANA_OTEL_LGTM}. + * + * @author Eddú Meléndez + */ +class GrafanaOpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.GRAFANA_OTEL_LGTM) + void runCreatesConnectionDetails(OtlpTracingConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUrl(Transport.HTTP)).startsWith("http://").endsWith("/v1/traces"); + assertThat(connectionDetails.getUrl(Transport.GRPC)).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/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..6f14c0a6d066 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests.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.otlp; + +import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +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 OpenTelemetryLoggingDockerComposeConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class OpenTelemetryLoggingDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.OPENTELEMETRY) + void runCreatesConnectionDetails(OtlpLoggingConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUrl(Transport.HTTP)).startsWith("http://").endsWith("/v1/logs"); + assertThat(connectionDetails.getUrl(Transport.GRPC)).startsWith("http://").endsWith("/v1/logs"); + } + +} 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 index 3ec07184e9e9..c885e10636c8 100644 --- 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 @@ -23,8 +23,8 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for - * {@link OpenTelemetryMetricsDockerComposeConnectionDetailsFactory}. + * Integration tests for {@link OpenTelemetryMetricsDockerComposeConnectionDetailsFactory} + * using {@link TestImage#OPENTELEMETRY}. * * @author Eddú Meléndez */ 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 index 97fc794c628b..a23a805ef51b 100644 --- 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 @@ -17,14 +17,15 @@ package org.springframework.boot.docker.compose.service.connection.otlp; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; 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}. + * Integration tests for {@link OpenTelemetryTracingDockerComposeConnectionDetailsFactory} + * using {@link TestImage#OPENTELEMETRY}. * * @author Eddú Meléndez */ @@ -32,7 +33,8 @@ class OpenTelemetryTracingDockerComposeConnectionDetailsFactoryIntegrationTests @DockerComposeTest(composeFile = "otlp-compose.yaml", image = TestImage.OPENTELEMETRY) void runCreatesConnectionDetails(OtlpTracingConnectionDetails connectionDetails) { - assertThat(connectionDetails.getUrl()).startsWith("http://").endsWith("/v1/traces"); + assertThat(connectionDetails.getUrl(Transport.HTTP)).startsWith("http://").endsWith("/v1/traces"); + assertThat(connectionDetails.getUrl(Transport.GRPC)).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 index c022cad841b6..609f8e5bf125 100644 --- 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 @@ -16,9 +16,15 @@ package org.springframework.boot.docker.compose.service.connection.postgres; +import java.sql.Driver; + 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; @@ -28,14 +34,45 @@ * @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); + } + + @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 index ef1a9322bcca..dab0edc7cf3e 100644 --- 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 @@ -16,11 +16,16 @@ 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; @@ -30,15 +35,46 @@ * @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/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/rabbit/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java index b774cff4f815..b6d6e8e7a840 100644 --- 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 @@ -29,11 +29,21 @@ * @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("/"); 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 index 1c1a35731233..8b6b3d46f5ac 100644 --- 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 @@ -29,16 +29,37 @@ * @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) { - Standalone standalone = connectionDetails.getStandalone(); + 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); diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/1.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/1.yaml new file mode 100644 index 000000000000..24ec799a8d21 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/1.yaml @@ -0,0 +1,6 @@ +services: + redis1: + profiles: [ 'dev' ] + image: '{imageName}' + ports: + - '6379' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/2.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/2.yaml new file mode 100644 index 000000000000..37b82aab595e --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/2.yaml @@ -0,0 +1,5 @@ +services: + redis2: + image: '{imageName}' + ports: + - '6379' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/3.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/3.yaml new file mode 100644 index 000000000000..32ef28fa5ec5 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/core/3.yaml @@ -0,0 +1,6 @@ +services: + redis3: + profiles: [ 'prod' ] + image: '{imageName}' + ports: + - '6379' 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/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/dockerTest/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 index b8d5ffd528e4..946fd4fd3590 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/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/clickhouse/clickhouse-bitnami-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/clickhouse/clickhouse-bitnami-compose.yaml new file mode 100644 index 000000000000..2d56633e7c47 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/clickhouse/clickhouse-bitnami-compose.yaml @@ -0,0 +1,9 @@ +services: + database: + image: '{imageName}' + ports: + - '8123' + environment: + - 'CLICKHOUSE_USER=myuser' + - 'CLICKHOUSE_PASSWORD=secret' + - 'CLICKHOUSE_DB=mydatabase' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/clickhouse/clickhouse-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/clickhouse/clickhouse-compose.yaml new file mode 100644 index 000000000000..2d56633e7c47 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/clickhouse/clickhouse-compose.yaml @@ -0,0 +1,9 @@ +services: + database: + image: '{imageName}' + ports: + - '8123' + environment: + - 'CLICKHOUSE_USER=myuser' + - 'CLICKHOUSE_PASSWORD=secret' + - 'CLICKHOUSE_DB=mydatabase' 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/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml new file mode 100644 index 000000000000..fde817d73f4d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml @@ -0,0 +1,7 @@ +services: + hazelcast: + image: '{imageName}' + environment: + HZ_CLUSTERNAME: "spring-boot" + ports: + - '5701' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml new file mode 100644 index 000000000000..89aaaaa9775d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml @@ -0,0 +1,5 @@ +services: + hazelcast: + image: '{imageName}' + ports: + - '5701' 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/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/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/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/mysql/mysql-bitnami-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 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-bitnami-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/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 index 258e73e333ee..86e05475417d 100644 --- 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 @@ -2,4 +2,5 @@ services: otlp: image: '{imageName}' ports: + - '4317' - '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/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/rabbit/rabbit-bitnami-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 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-bitnami-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/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/DockerCli.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java index 160e156a241b..3f92915ea5b5 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.springframework.boot.docker.compose.core.DockerCliCommand.Type; import org.springframework.boot.logging.LogLevel; import org.springframework.core.log.LogMessage; +import org.springframework.util.CollectionUtils; /** * Wrapper around {@code docker} and {@code docker-compose} command line tools. @@ -49,22 +50,18 @@ class DockerCli { private final DockerCommands dockerCommands; - private final DockerComposeFile composeFile; - - private final Set activeProfiles; + private final DockerComposeOptions dockerComposeOptions; /** * Create a new {@link DockerCli} instance. * @param workingDirectory the working directory or {@code null} - * @param composeFile the docker compose file to use - * @param activeProfiles the docker compose profiles to activate + * @param dockerComposeOptions the Docker Compose options to use or {@code null}. */ - DockerCli(File workingDirectory, DockerComposeFile composeFile, Set activeProfiles) { + DockerCli(File workingDirectory, DockerComposeOptions dockerComposeOptions) { this.processRunner = new ProcessRunner(workingDirectory); this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory, (key) -> new DockerCommands(this.processRunner)); - this.composeFile = composeFile; - this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet(); + this.dockerComposeOptions = (dockerComposeOptions != null) ? dockerComposeOptions : DockerComposeOptions.none(); } /** @@ -93,15 +90,25 @@ private List createCommand(Type type) { case DOCKER -> new ArrayList<>(this.dockerCommands.get(type)); case DOCKER_COMPOSE -> { List result = new ArrayList<>(this.dockerCommands.get(type)); - if (this.composeFile != null) { - result.add("--file"); - result.add(this.composeFile.toString()); + DockerComposeFile composeFile = this.dockerComposeOptions.composeFile(); + if (composeFile != null) { + for (File file : composeFile.getFiles()) { + result.add("--file"); + result.add(file.getPath()); + } } result.add("--ansi"); result.add("never"); - for (String profile : this.activeProfiles) { - result.add("--profile"); - result.add(profile); + Set activeProfiles = this.dockerComposeOptions.activeProfiles(); + if (!CollectionUtils.isEmpty(activeProfiles)) { + for (String profile : activeProfiles) { + result.add("--profile"); + result.add(profile); + } + } + List arguments = this.dockerComposeOptions.arguments(); + if (!CollectionUtils.isEmpty(arguments)) { + result.addAll(arguments); } yield result; } @@ -110,10 +117,10 @@ private List createCommand(Type type) { /** * Return the {@link DockerComposeFile} being used by this CLI instance. - * @return the docker compose file + * @return the Docker Compose file */ DockerComposeFile getDockerComposeFile() { - return this.composeFile; + return this.dockerComposeOptions.composeFile(); } /** @@ -154,7 +161,7 @@ private List getDockerComposeCommand(ProcessRunner processRunner) { DockerCliComposeVersionResponse response = DockerJson.deserialize( processRunner.run("docker", "compose", "version", "--format", "json"), DockerCliComposeVersionResponse.class); - logger.trace(LogMessage.format("Using docker compose %s", response.version())); + logger.trace(LogMessage.format("Using Docker Compose %s", response.version())); return List.of("docker", "compose"); } catch (ProcessExitException ex) { @@ -183,4 +190,22 @@ List get(Type type) { } + /** + * Options for Docker Compose. + * + * @param composeFile the Docker Compose file to use + * @param activeProfiles the profiles to activate + * @param arguments the arguments to pass to Docker Compose + */ + record DockerComposeOptions(DockerComposeFile composeFile, Set activeProfiles, List arguments) { + DockerComposeOptions { + activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet(); + arguments = (arguments != null) ? arguments : Collections.emptyList(); + } + + static DockerComposeOptions none() { + return new DockerComposeOptions(null, null, null); + } + } + } 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/DockerCliComposeVersionResponse.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposeVersionResponse.java index 147ed952e387..9b39406cc851 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposeVersionResponse.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposeVersionResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 @@ /** * Response from {@code docker compose version}. * - * @param version docker compose version + * @param version the Docker Compose version * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb 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..129be08a0735 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. @@ -17,9 +17,11 @@ package org.springframework.boot.docker.compose.core; import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Set; +import org.springframework.boot.docker.compose.core.DockerCli.DockerComposeOptions; import org.springframework.boot.logging.LogLevel; /** @@ -44,6 +46,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 +62,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 +78,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 +94,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. @@ -82,14 +120,29 @@ public interface DockerCompose { /** * Factory method used to create a {@link DockerCompose} instance. - * @param file the docker compose file + * @param file the Docker Compose file * @param hostname the hostname used for services or {@code null} if the hostname * should be deduced * @param activeProfiles a set of the profiles that should be activated * @return a {@link DockerCompose} instance */ static DockerCompose get(DockerComposeFile file, String hostname, Set activeProfiles) { - DockerCli cli = new DockerCli(null, file, activeProfiles); + return get(file, hostname, activeProfiles, Collections.emptyList()); + } + + /** + * Factory method used to create a {@link DockerCompose} instance. + * @param file the Docker Compose file + * @param hostname the hostname used for services or {@code null} if the hostname + * should be deduced + * @param activeProfiles a set of the profiles that should be activated + * @param arguments the arguments to pass to Docker Compose + * @return a {@link DockerCompose} instance + * @since 3.4.0 + */ + static DockerCompose get(DockerComposeFile file, String hostname, Set activeProfiles, + List arguments) { + DockerCli cli = new DockerCli(null, new DockerComposeOptions(file, activeProfiles, arguments)); return new DefaultDockerCompose(cli, hostname); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java index 8c58e7fef499..30156ed4c46b 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,18 +21,22 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.springframework.util.Assert; /** - * A reference to a docker compose file (usually named {@code compose.yaml}). + * A reference to a Docker Compose file (usually named {@code compose.yaml}). * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb * @since 3.1.0 * @see #of(File) + * @see #of(Collection) * @see #find(File) */ public final class DockerComposeFile { @@ -40,17 +44,31 @@ public final class DockerComposeFile { private static final List SEARCH_ORDER = List.of("compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"); - private final File file; + private final List files; - private DockerComposeFile(File file) { + private DockerComposeFile(List files) { + Assert.state(!files.isEmpty(), "Files must not be empty"); + this.files = files.stream().map(DockerComposeFile::toCanonicalFile).toList(); + } + + private static File toCanonicalFile(File file) { try { - this.file = file.getCanonicalFile(); + return file.getCanonicalFile(); } catch (IOException ex) { throw new UncheckedIOException(ex); } } + /** + * Returns the source Docker Compose files. + * @return the source Docker Compose files + * @since 3.4.0 + */ + public List getFiles() { + return this.files; + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -60,21 +78,24 @@ public boolean equals(Object obj) { return false; } DockerComposeFile other = (DockerComposeFile) obj; - return this.file.equals(other.file); + return this.files.equals(other.files); } @Override public int hashCode() { - return this.file.hashCode(); + return this.files.hashCode(); } @Override public String toString() { - return this.file.toString(); + if (this.files.size() == 1) { + return this.files.get(0).getPath(); + } + return this.files.stream().map(File::toString).collect(Collectors.joining(", ")); } /** - * Find the docker compose file by searching in the given working directory. Files are + * Find the Docker Compose file by searching in the given working directory. Files are * considered in the same order that {@code docker compose} uses, namely: *
    *
  • {@code compose.yaml}
  • @@ -84,7 +105,7 @@ public String toString() { *
* @param workingDirectory the working directory to search or {@code null} to use the * current directory - * @return the located file or {@code null} if no docker compose file can be found + * @return the located file or {@code null} if no Docker Compose file can be found */ public static DockerComposeFile find(File workingDirectory) { File base = (workingDirectory != null) ? workingDirectory : new File("."); @@ -105,13 +126,29 @@ public static DockerComposeFile find(File workingDirectory) { /** * Create a new {@link DockerComposeFile} for the given {@link File}. * @param file the source file - * @return the docker compose file + * @return the Docker Compose file */ public static DockerComposeFile of(File file) { Assert.notNull(file, "File must not be null"); Assert.isTrue(file.exists(), () -> "Docker Compose file '%s' does not exist".formatted(file)); Assert.isTrue(file.isFile(), () -> "Docker compose file '%s' is not a file".formatted(file)); - return new DockerComposeFile(file); + return new DockerComposeFile(Collections.singletonList(file)); + } + + /** + * Creates a new {@link DockerComposeFile} for the given {@link File files}. + * @param files the source files + * @return the Docker Compose file + * @since 3.4.0 + */ + public static DockerComposeFile of(Collection files) { + Assert.notNull(files, "Files must not be null"); + for (File file : files) { + Assert.notNull(file, "File must not be null"); + Assert.isTrue(file.exists(), () -> "Docker Compose file '%s' does not exist".formatted(file)); + Assert.isTrue(file.isFile(), () -> "Docker compose file '%s' is not a file".formatted(file)); + } + return new DockerComposeFile(List.copyOf(files)); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java index ed2fb98de9ae..623747e6081f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 org.springframework.boot.origin.Origin; /** - * An origin which points to a service defined in docker compose. + * An origin which points to a service defined in Docker Compose. * - * @param composeFile docker compose file - * @param serviceName name of the docker compose service + * @param composeFile the Docker Compose file + * @param serviceName name of the Docker Compose service * @author Moritz Halbritter * @author Andy Wilkinson * @since 3.1.0 @@ -31,7 +31,7 @@ public record DockerComposeOrigin(DockerComposeFile composeFile, String serviceN @Override public String toString() { - return "Docker compose service '%s' defined in '%s'".formatted(this.serviceName, + return "Docker compose service '%s' defined in %s".formatted(this.serviceName, (this.composeFile != null) ? this.composeFile : "default compose file"); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java index 6cb7be8548a3..eefc8caffe7d 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 @@ import java.util.Map; /** - * Provides details of a running docker compose service. + * Provides details of a running Docker Compose service. * * @author Moritz Halbritter * @author Andy Wilkinson diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java index fff829479d3b..592b0d6e747e 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Core interfaces and classes for working with docker compose. + * Core interfaces and classes for working with Docker Compose. */ package org.springframework.boot.docker.compose.core; 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..9611022fcbda 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,15 +32,18 @@ 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; +import org.springframework.context.aot.AbstractAotProcessor; import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** - * Manages the lifecycle for docker compose services. + * Manages the lifecycle for Docker Compose services. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -93,7 +96,7 @@ class DockerComposeLifecycleManager { } void start() { - if (Boolean.getBoolean("spring.aot.processing") || AotDetector.useGeneratedArtifacts()) { + if (Boolean.getBoolean(AbstractAotProcessor.AOT_PROCESSING) || AotDetector.useGeneratedArtifacts()) { logger.trace("Docker Compose support disabled with AOT and native images"); return; } @@ -107,9 +110,10 @@ void start() { } DockerComposeFile composeFile = getComposeFile(); Set activeProfiles = this.properties.getProfiles().getActive(); - DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles); + List arguments = this.properties.getArguments(); + DockerCompose dockerCompose = getDockerCompose(composeFile, activeProfiles, arguments); if (!dockerCompose.hasDefinedServices()) { - logger.warn(LogMessage.format("No services defined in Docker Compose file '%s' with active profiles %s", + logger.warn(LogMessage.format("No services defined in Docker Compose file %s with active profiles %s", composeFile, activeProfiles)); return; } @@ -119,19 +123,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); @@ -142,16 +148,22 @@ void start() { } protected DockerComposeFile getComposeFile() { - DockerComposeFile composeFile = (this.properties.getFile() != null) - ? DockerComposeFile.of(this.properties.getFile()) : DockerComposeFile.find(this.workingDirectory); + DockerComposeFile composeFile = (CollectionUtils.isEmpty(this.properties.getFile())) + ? DockerComposeFile.find(this.workingDirectory) : DockerComposeFile.of(this.properties.getFile()); Assert.state(composeFile != null, () -> "No Docker Compose file found in directory '%s'".formatted( ((this.workingDirectory != null) ? this.workingDirectory : new File(".")).toPath().toAbsolutePath())); - logger.info(LogMessage.format("Using Docker Compose file '%s'", composeFile)); + if (composeFile.getFiles().size() == 1) { + logger.info(LogMessage.format("Using Docker Compose file %s", composeFile.getFiles().get(0))); + } + else { + logger.info(LogMessage.format("Using Docker Compose files %s", composeFile.toString())); + } return composeFile; } - protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set activeProfiles) { - return DockerCompose.get(composeFile, this.properties.getHost(), activeProfiles); + protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set activeProfiles, + List arguments) { + return DockerCompose.get(composeFile, this.properties.getHost(), activeProfiles, arguments); } private boolean isIgnored(RunningService service) { 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..d6e98ec0f0e2 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; /** @@ -39,14 +42,19 @@ public class DockerComposeProperties { static final String NAME = "spring.docker.compose"; /** - * Whether docker compose support is enabled. + * Whether Docker Compose support is enabled. */ private boolean enabled = true; /** - * Path to a specific docker compose configuration file. + * Arguments to pass to the Docker Compose command. */ - private File file; + private final List arguments = new ArrayList<>(); + + /** + * Paths to the Docker Compose configuration files. + */ + private final List file = new ArrayList<>(); /** * Docker compose lifecycle management. @@ -85,12 +93,12 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public File getFile() { - return this.file; + public List getArguments() { + return this.arguments; } - public void setFile(File file) { - this.file = file; + public List getFile() { + return this.file; } public LifecycleManagement getLifecycleManagement() { @@ -139,7 +147,7 @@ static DockerComposeProperties get(Binder binder) { public static class Start { /** - * Command used to start docker compose. + * Command used to start Docker Compose. */ private StartCommand command = StartCommand.UP; @@ -148,6 +156,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 +182,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 ""; + } + + } + } /** @@ -172,7 +239,7 @@ public void setLogLevel(LogLevel logLevel) { public static class Stop { /** - * Command used to stop docker compose. + * Command used to stop Docker Compose. */ private StopCommand command = StopCommand.STOP; @@ -181,6 +248,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 +269,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/DockerComposeServicesReadyEvent.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeServicesReadyEvent.java index 6b07df548eab..08cb30efd52f 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeServicesReadyEvent.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeServicesReadyEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 ApplicationContext getSource() { } /** - * Return the relevant docker compose services that are running. + * Return the relevant Docker Compose services that are running. * @return the running services */ public List getRunningServices() { 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..4db5c1a0db9c 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; @@ -41,14 +41,21 @@ public enum StartCommand { */ START(DockerCompose::start); - private final BiConsumer action; + private final Command command; - StartCommand(BiConsumer action) { - this.action = action; + StartCommand(Command command) { + this.command = command; } - void applyTo(DockerCompose dockerCompose, LogLevel logLevel) { - this.action.accept(dockerCompose, logLevel); + void applyTo(DockerCompose dockerCompose, LogLevel logLevel, List arguments) { + this.command.applyTo(dockerCompose, logLevel, arguments); + } + + @FunctionalInterface + private interface Command { + + 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..2deaf5d1935a 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; @@ -41,14 +41,21 @@ public enum StopCommand { */ STOP(DockerCompose::stop); - private final BiConsumer action; + private final Command command; - StopCommand(BiConsumer action) { - this.action = action; + StopCommand(Command command) { + this.command = command; } - void applyTo(DockerCompose dockerCompose, Duration timeout) { - this.action.accept(dockerCompose, timeout); + void applyTo(DockerCompose dockerCompose, Duration timeout, List arguments) { + this.command.applyTo(dockerCompose, timeout, arguments); + } + + @FunctionalInterface + private interface Command { + + 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/DockerComposeConnectionSource.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionSource.java index 1009069dcbce..4ddcc877612c 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionSource.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ /** * Passed to {@link DockerComposeConnectionDetailsFactory} to provide details of the - * {@link RunningService running docker compose service}. + * {@link RunningService running Docker Compose service}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -34,14 +34,14 @@ public final class DockerComposeConnectionSource { /** * Create a new {@link DockerComposeConnectionSource} instance. - * @param runningService the running docker compose service + * @param runningService the running Docker Compose service */ DockerComposeConnectionSource(RunningService runningService) { this.runningService = runningService; } /** - * Return the running docker compose service. + * Return the running Docker Compose service. * @return the running service */ public RunningService getRunningService() { diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java index 03efb04de0c8..4cf5135d26ed 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.container.ContainerImageMetadata; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; import org.springframework.boot.docker.compose.core.RunningService; @@ -77,10 +78,13 @@ private void registerConnectionDetails(BeanDefinitionRegistry registry, List void register(BeanDefinitionRegistry registry, RunningService runningService, Class connectionDetailsType, ConnectionDetails connectionDetails) { + ContainerImageMetadata containerMetadata = new ContainerImageMetadata(runningService.image().toString()); String beanName = getBeanName(runningService, connectionDetailsType); Class beanType = (Class) connectionDetails.getClass(); Supplier beanSupplier = () -> (T) connectionDetails; - registry.registerBeanDefinition(beanName, new RootBeanDefinition(beanType, beanSupplier)); + RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType, beanSupplier); + containerMetadata.addTo(beanDefinition); + registry.registerBeanDefinition(beanName, beanDefinition); } private String getBeanName(RunningService runningService, Class connectionDetailsType) { 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 index 30597c45a8dd..0a8c42143856 100644 --- 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 @@ -42,7 +42,7 @@ protected ActiveMQConnectionDetails getDockerComposeConnectionDetails(DockerComp } /** - * {@link ActiveMQConnectionDetails} backed by a {@code activemq} + * {@link ActiveMQConnectionDetails} backed by an {@code activemq} * {@link RunningService}. */ static class ActiveMQDockerComposeConnectionDetails extends DockerComposeConnectionDetails 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 index 5cb2e75cf5b4..0abe3f456b5c 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose ActiveMQ service connections. + * 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/cassandra/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/package-info.java index 86d5788526e1..b8667d6389ca 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/cassandra/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Cassandra service connections. + * Auto-configuration for Docker Compose Cassandra service connections. */ package org.springframework.boot.docker.compose.service.connection.cassandra; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseEnvironment.java new file mode 100644 index 000000000000..3414ce666291 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseEnvironment.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.clickhouse; + +import java.util.Map; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * ClickHouse environment details. + * + * @author Stephane Nicoll + */ +class ClickHouseEnvironment { + + private final String username; + + private final String password; + + private final String database; + + ClickHouseEnvironment(Map env) { + this.username = env.getOrDefault("CLICKHOUSE_USER", "default"); + this.password = extractPassword(env); + this.database = env.getOrDefault("CLICKHOUSE_DB", "default"); + } + + private String extractPassword(Map env) { + boolean allowEmpty = Boolean.parseBoolean(env.getOrDefault("ALLOW_EMPTY_PASSWORD", Boolean.FALSE.toString())); + String password = env.get("CLICKHOUSE_PASSWORD"); + Assert.state(StringUtils.hasLength(password) || allowEmpty, "No ClickHouse password found"); + return (password != null) ? password : ""; + } + + String getUsername() { + return this.username; + } + + String getPassword() { + return this.password; + } + + String getDatabase() { + return this.database; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseJdbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseJdbcDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..331feb3f5cfc --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseJdbcDockerComposeConnectionDetailsFactory.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.clickhouse; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +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; +import org.springframework.boot.docker.compose.service.connection.jdbc.JdbcUrlBuilder; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link JdbcConnectionDetails} + * for a {@code clickhouse} service. + * + * @author Stephane Nicoll + */ +class ClickHouseJdbcDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final String[] CLICKHOUSE_CONTAINER_NAMES = { "clickhouse/clickhouse-server", "bitnami/clickhouse" }; + + protected ClickHouseJdbcDockerComposeConnectionDetailsFactory() { + super(CLICKHOUSE_CONTAINER_NAMES); + } + + @Override + protected JdbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ClickhouseJdbcDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link JdbcConnectionDetails} backed by a {@code clickhouse} + * {@link RunningService}. + */ + static class ClickhouseJdbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements JdbcConnectionDetails { + + private static final JdbcUrlBuilder jdbcUrlBuilder = new JdbcUrlBuilder("clickhouse", 8123); + + private final ClickHouseEnvironment environment; + + private final String jdbcUrl; + + ClickhouseJdbcDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ClickHouseEnvironment(service.env()); + this.jdbcUrl = jdbcUrlBuilder.build(service, this.environment.getDatabase()); + } + + @Override + public String getUsername() { + return this.environment.getUsername(); + } + + @Override + public String getPassword() { + return this.environment.getPassword(); + } + + @Override + public String getJdbcUrl() { + return this.jdbcUrl; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseR2dbcDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseR2dbcDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..dfb64867b13d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseR2dbcDockerComposeConnectionDetailsFactory.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.clickhouse; + +import io.r2dbc.spi.ConnectionFactoryOptions; + +import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; +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; +import org.springframework.boot.docker.compose.service.connection.r2dbc.ConnectionFactoryOptionsBuilder; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} + * for a {@code clickhouse} service. + * + * @author Stephane Nicoll + */ +class ClickHouseR2dbcDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final String[] CLICKHOUSE_CONTAINER_NAMES = { "clickhouse/clickhouse-server", "bitnami/clickhouse" }; + + ClickHouseR2dbcDockerComposeConnectionDetailsFactory() { + super(CLICKHOUSE_CONTAINER_NAMES, "io.r2dbc.spi.ConnectionFactoryOptions"); + } + + @Override + protected R2dbcConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ClickhouseDbR2dbcDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link R2dbcConnectionDetails} backed by a {@code clickhouse} + * {@link RunningService}. + */ + static class ClickhouseDbR2dbcDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements R2dbcConnectionDetails { + + private static final ConnectionFactoryOptionsBuilder connectionFactoryOptionsBuilder = new ConnectionFactoryOptionsBuilder( + "clickhouse", 8123); + + private final ConnectionFactoryOptions connectionFactoryOptions; + + ClickhouseDbR2dbcDockerComposeConnectionDetails(RunningService service) { + super(service); + ClickHouseEnvironment environment = new ClickHouseEnvironment(service.env()); + this.connectionFactoryOptions = connectionFactoryOptionsBuilder.build(service, environment.getDatabase(), + environment.getUsername(), environment.getPassword()); + } + + @Override + public ConnectionFactoryOptions getConnectionFactoryOptions() { + return this.connectionFactoryOptions; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/package-info.java new file mode 100644 index 000000000000..9ec09361e8ba --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/clickhouse/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 ClickHouse service connections. + */ +package org.springframework.boot.docker.compose.service.connection.clickhouse; 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/elasticsearch/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/package-info.java index 875262ec4268..401ffee23a26 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/elasticsearch/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Elasticsearch service connections. + * Auto-configuration for Docker Compose Elasticsearch service connections. */ package org.springframework.boot.docker.compose.service.connection.elasticsearch; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/flyway/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/flyway/package-info.java index 11d3a5d1499f..35ddace85a92 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/flyway/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/flyway/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Flyway service connections. + * Auto-configuration for Docker Compose Flyway service connections. */ package org.springframework.boot.docker.compose.service.connection.flyway; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..ad86ca1d6568 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.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.hazelcast; + +import com.hazelcast.client.config.ClientConfig; + +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +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 HazelcastConnectionDetails} for a {@code hazelcast} service. + * + * @author Dmytro Nosan + */ +class HazelcastDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int DEFAULT_PORT = 5701; + + protected HazelcastDockerComposeConnectionDetailsFactory() { + super("hazelcast/hazelcast", "com.hazelcast.client.config.ClientConfig"); + } + + @Override + protected HazelcastConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new HazelcastDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link HazelcastConnectionDetails} backed by a {@code hazelcast} + * {@link RunningService}. + */ + static class HazelcastDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements HazelcastConnectionDetails { + + private final String host; + + private final int port; + + private final HazelcastEnvironment environment; + + HazelcastDockerComposeConnectionDetails(RunningService service) { + super(service); + this.host = service.host(); + this.port = service.ports().get(DEFAULT_PORT); + this.environment = new HazelcastEnvironment(service.env()); + } + + @Override + public ClientConfig getClientConfig() { + ClientConfig config = new ClientConfig(); + if (this.environment.getClusterName() != null) { + config.setClusterName(this.environment.getClusterName()); + } + config.getNetworkConfig().addAddress(this.host + ":" + this.port); + return config; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java new file mode 100644 index 000000000000..dc4544c8a3cc --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.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.hazelcast; + +import java.util.Map; + +/** + * Hazelcast environment details. + * + * @author Dmytro Nosan + */ +class HazelcastEnvironment { + + private final String clusterName; + + HazelcastEnvironment(Map env) { + this.clusterName = env.get("HZ_CLUSTERNAME"); + } + + String getClusterName() { + return this.clusterName; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java new file mode 100644 index 000000000000..b1798b8a14c4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/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 Hazelcast service connections. + */ +package org.springframework.boot.docker.compose.service.connection.hazelcast; 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/liquibase/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/liquibase/package-info.java index de6049f6201c..e5e285eaeedc 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/liquibase/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/liquibase/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Liquibase service connections. + * Auto-configuration for Docker Compose Liquibase service connections. */ package org.springframework.boot.docker.compose.service.connection.liquibase; 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/mariadb/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/package-info.java index 7d847e216d0d..50f13a700315 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mariadb/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose MariaDB service connections. + * Auto-configuration for Docker Compose MariaDB service connections. */ package org.springframework.boot.docker.compose.service.connection.mariadb; 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/mongo/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/package-info.java index f912ab7f052e..bdb30b2bb1bc 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mongo/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose MongoDB service connections. + * Auto-configuration for Docker Compose MongoDB service connections. */ package org.springframework.boot.docker.compose.service.connection.mongo; 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/mysql/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/package-info.java index aa5ffe1ca9ae..9412cccf4942 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/mysql/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose MySQL service connections. + * Auto-configuration for Docker Compose MySQL service connections. */ package org.springframework.boot.docker.compose.service.connection.mysql; 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 index 33bd622e23ab..5b58d6d1d0df 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,14 @@ * 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"); + super(NEO4J_CONTAINER_NAMES); } @Override 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 index 59e5a90e9230..eaad04475acb 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,13 +25,18 @@ * Neo4j environment details. * * @author Andy Wilkinson + * @author Scott Frederick */ class Neo4jEnvironment { private final AuthToken authToken; Neo4jEnvironment(Map env) { - this.authToken = parse(env.get("NEO4J_AUTH")); + 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) { 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 index afea67c3cf5c..a4c7fbcbc871 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Neo4j service connections. + * 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/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/package-info.java index e12c74290f4e..c1529330ecc3 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/oracle/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose MySQL service connections. + * Auto-configuration for Docker Compose MySQL service connections. */ package org.springframework.boot.docker.compose.service.connection.oracle; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..92ea28f71732 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/otlp/OpenTelemetryLoggingDockerComposeConnectionDetailsFactory.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.otlp; + +import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +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 OtlpLoggingConnectionDetails} for an OTLP service. + * + * @author Eddú Meléndez + */ +class OpenTelemetryLoggingDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final String[] OPENTELEMETRY_IMAGE_NAMES = { "otel/opentelemetry-collector-contrib", + "grafana/otel-lgtm" }; + + private static final int OTLP_GRPC_PORT = 4317; + + private static final int OTLP_HTTP_PORT = 4318; + + OpenTelemetryLoggingDockerComposeConnectionDetailsFactory() { + super(OPENTELEMETRY_IMAGE_NAMES, + "org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); + } + + @Override + protected OtlpLoggingConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new OpenTelemetryLoggingDockerComposeConnectionDetails(source.getRunningService()); + } + + private static final class OpenTelemetryLoggingDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements OtlpLoggingConnectionDetails { + + private final String host; + + private final int grpcPort; + + private final int httPort; + + private OpenTelemetryLoggingDockerComposeConnectionDetails(RunningService source) { + super(source); + this.host = source.host(); + this.grpcPort = source.ports().get(OTLP_GRPC_PORT); + this.httPort = source.ports().get(OTLP_HTTP_PORT); + } + + @Override + public String getUrl(Transport transport) { + int port = switch (transport) { + case HTTP -> this.httPort; + case GRPC -> this.grpcPort; + }; + return "http://%s:%d/v1/logs".formatted(this.host, port); + } + + } + +} 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 index 49913297040c..419bf357bee8 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 +30,13 @@ class OpenTelemetryMetricsDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + private static final String[] OPENTELEMETRY_IMAGE_NAMES = { "otel/opentelemetry-collector-contrib", + "grafana/otel-lgtm" }; + private static final int OTLP_PORT = 4318; OpenTelemetryMetricsDockerComposeConnectionDetailsFactory() { - super("otel/opentelemetry-collector-contrib", + super(OPENTELEMETRY_IMAGE_NAMES, "org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration"); } 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 index 20e5b06b3daa..725600e063af 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.docker.compose.service.connection.otlp; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; 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; @@ -26,15 +27,21 @@ * {@link OtlpTracingConnectionDetails} for an OTLP service. * * @author Eddú Meléndez + * @author Moritz Halbritter */ class OpenTelemetryTracingDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { - private static final int OTLP_PORT = 4318; + private static final String[] OPENTELEMETRY_IMAGE_NAMES = { "otel/opentelemetry-collector-contrib", + "grafana/otel-lgtm" }; + + private static final int OTLP_GRPC_PORT = 4317; + + private static final int OTLP_HTTP_PORT = 4318; OpenTelemetryTracingDockerComposeConnectionDetailsFactory() { - super("otel/opentelemetry-collector-contrib", - "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration"); + super(OPENTELEMETRY_IMAGE_NAMES, + "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration"); } @Override @@ -47,17 +54,24 @@ private static final class OpenTelemetryTracingDockerComposeConnectionDetails ex private final String host; - private final int port; + private final int grpcPort; + + private final int httPort; private OpenTelemetryTracingDockerComposeConnectionDetails(RunningService source) { super(source); this.host = source.host(); - this.port = source.ports().get(OTLP_PORT); + this.grpcPort = source.ports().get(OTLP_GRPC_PORT); + this.httPort = source.ports().get(OTLP_HTTP_PORT); } @Override - public String getUrl() { - return "http://%s:%d/v1/traces".formatted(this.host, this.port); + public String getUrl(Transport transport) { + int port = switch (transport) { + case HTTP -> this.httPort; + case GRPC -> this.grpcPort; + }; + return "http://%s:%d/v1/traces".formatted(this.host, 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 index cbac91d2c639..3a6c89de0bde 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Support for docker compose OpenTelemetry service connections. + * 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..0f0055b68df9 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 (isUsingTrustHostAuthMethod(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 isUsingTrustHostAuthMethod(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/postgres/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/package-info.java index e7771e245a89..5b7a5ce53655 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/postgres/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Postgres service connections. + * Auto-configuration for Docker Compose Postgres service connections. */ package org.springframework.boot.docker.compose.service.connection.postgres; 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 index 7d8c4d1b1a56..ddc8cda0c1ee 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Pulsar service connections. + * 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/rabbit/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/package-info.java index 7f8975636a3f..e9bdb29fc0ed 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/rabbit/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose RabbitMQ service connections. + * Auto-configuration for Docker Compose RabbitMQ service connections. */ package org.springframework.boot.docker.compose.service.connection.rabbit; 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/redis/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/package-info.java index a59d81ce62b4..0ec738a060ce 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Redis service connections. + * Auto-configuration for Docker Compose Redis service connections. */ package org.springframework.boot.docker.compose.service.connection.redis; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/package-info.java index adff96befd79..d7ce4badbaa8 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/sqlserver/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose MS SQL Server service connections. + * Auto-configuration for Docker Compose MS SQL Server service connections. */ package org.springframework.boot.docker.compose.service.connection.sqlserver; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/zipkin/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/zipkin/package-info.java index b9bf53cef3c2..16ecf6b4e92b 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/zipkin/package-info.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/zipkin/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for docker compose Zipkin service connections. + * Auto-configuration for Docker Compose Zipkin service connections. */ package org.springframework.boot.docker.compose.service.connection.zipkin; 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 deleted file mode 100644 index 4ec0d8d247bb..000000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "groups": [], - "hints": [], - "properties": [ - { - "name": "spring.docker.compose.lifecycle-management", - "defaultValue": "start-and-stop" - }, - { - "name": "spring.docker.compose.readiness.wait", - "defaultValue": "always" - }, - { - "name": "spring.docker.compose.start.command", - "defaultValue": "up" - }, - { - "name": "spring.docker.compose.start.log-level", - "defaultValue": "info" - }, - { - "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 f00fc1938155..ad1a5dfc2416 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,10 +5,16 @@ 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.clickhouse.ClickHouseJdbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.clickhouse.ClickHouseR2dbcDockerComposeConnectionDetailsFactory,\ 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.hazelcast.HazelcastDockerComposeConnectionDetailsFactory,\ +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,\ @@ -17,9 +23,10 @@ org.springframework.boot.docker.compose.service.connection.mysql.MySqlJdbcDocker org.springframework.boot.docker.compose.service.connection.mysql.MySqlR2dbcDockerComposeConnectionDetailsFactory,\ 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.OracleXeJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.oracle.OracleXeR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.otlp.OpenTelemetryLoggingDockerComposeConnectionDetailsFactory,\ 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,\ 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/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 02bab15eb46c..95d3892d69e2 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.io.IOException; +import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -59,22 +60,30 @@ void toStringReturnsFileName() throws Exception { assertThat(composeFile.toString()).endsWith(File.separator + "compose.yml"); } + @Test + void toStringReturnsFileNameList() throws Exception { + File file1 = createTempFile("1.yml"); + File file2 = createTempFile("2.yml"); + DockerComposeFile composeFile = DockerComposeFile.of(List.of(file1, file2)); + assertThat(composeFile).hasToString(file1 + ", " + file2); + } + @Test void findFindsSingleFile() throws Exception { - File file = new File(this.temp, "docker-compose.yml"); + File file = new File(this.temp, "docker-compose.yml").getCanonicalFile(); FileCopyUtils.copy(new byte[0], file); DockerComposeFile composeFile = DockerComposeFile.find(file.getParentFile()); - assertThat(composeFile.toString()).endsWith(File.separator + "docker-compose.yml"); + assertThat(composeFile.getFiles()).containsExactly(file); } @Test void findWhenMultipleFilesPicksBest() throws Exception { - File f1 = new File(this.temp, "docker-compose.yml"); + File f1 = new File(this.temp, "docker-compose.yml").getCanonicalFile(); FileCopyUtils.copy(new byte[0], f1); - File f2 = new File(this.temp, "compose.yml"); + File f2 = new File(this.temp, "compose.yml").getCanonicalFile(); FileCopyUtils.copy(new byte[0], f2); DockerComposeFile composeFile = DockerComposeFile.find(f1.getParentFile()); - assertThat(composeFile.toString()).endsWith(File.separator + "compose.yml"); + assertThat(composeFile.getFiles()).containsExactly(f2); } @Test @@ -94,24 +103,31 @@ void findWhenWorkingDirectoryDoesNotExistReturnsNull() { @Test void findWhenWorkingDirectoryIsNotDirectoryThrowsException() throws Exception { - File file = new File(this.temp, "iamafile"); - FileCopyUtils.copy(new byte[0], file); + File file = createTempFile("iamafile"); assertThatIllegalArgumentException().isThrownBy(() -> DockerComposeFile.find(file)) .withMessageEndingWith("is not a directory"); } @Test void ofReturnsDockerComposeFile() throws Exception { - File file = new File(this.temp, "anyfile.yml"); - FileCopyUtils.copy(new byte[0], file); + File file = createTempFile("compose.yml"); DockerComposeFile composeFile = DockerComposeFile.of(file); assertThat(composeFile).isNotNull(); - assertThat(composeFile).hasToString(file.getCanonicalPath()); + assertThat(composeFile.getFiles()).containsExactly(file); + } + + @Test + void ofWithMultipleFilesReturnsDockerComposeFile() throws Exception { + File file1 = createTempFile("1.yml"); + File file2 = createTempFile("2.yml"); + DockerComposeFile composeFile = DockerComposeFile.of(List.of(file1, file2)); + assertThat(composeFile).isNotNull(); + assertThat(composeFile.getFiles()).containsExactly(file1, file2); } @Test void ofWhenFileIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> DockerComposeFile.of(null)) + assertThatIllegalArgumentException().isThrownBy(() -> DockerComposeFile.of((File) null)) .withMessage("File must not be null"); } @@ -129,9 +145,13 @@ void ofWhenFileIsNotFileThrowsException() { } private DockerComposeFile createComposeFile(String name) throws IOException { + return DockerComposeFile.of(createTempFile(name)); + } + + private File createTempFile(String name) throws IOException { File file = new File(this.temp, name); FileCopyUtils.copy(new byte[0], file); - return DockerComposeFile.of(file); + return file.getCanonicalFile(); } } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeOriginTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeOriginTests.java index 7d59606e64c3..592216fbcba2 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeOriginTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/core/DockerComposeOriginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.io.IOException; +import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -42,8 +43,17 @@ class DockerComposeOriginTests { void hasToString() throws Exception { DockerComposeFile composeFile = createTempComposeFile(); DockerComposeOrigin origin = new DockerComposeOrigin(composeFile, "service-1"); - assertThat(origin.toString()).startsWith("Docker compose service 'service-1' defined in '") - .endsWith("compose.yaml'"); + assertThat(origin.toString()).startsWith("Docker compose service 'service-1' defined in ") + .endsWith("compose.yaml"); + } + + @Test + void hasToStringWithMultipleFiles() throws IOException { + File file1 = createTempFile("1.yaml"); + File file2 = createTempFile("2.yaml"); + DockerComposeOrigin origin = new DockerComposeOrigin(DockerComposeFile.of(List.of(file1, file2)), "service-1"); + assertThat(origin.toString()) + .startsWith("Docker compose service 'service-1' defined in %s, %s".formatted(file1, file2)); } @Test @@ -63,9 +73,13 @@ void equalsAndHashcode() throws Exception { } private DockerComposeFile createTempComposeFile() throws IOException { - File file = new File(this.temp, "compose.yaml"); + return DockerComposeFile.of(createTempFile("compose.yaml")); + } + + private File createTempFile(String filename) throws IOException { + File file = new File(this.temp, filename); FileCopyUtils.copy(new byte[0], file); - return DockerComposeFile.of(file); + return file.getCanonicalFile(); } } 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..74e5d851fd8c 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,10 +39,12 @@ 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; import org.springframework.context.ApplicationListener; +import org.springframework.context.aot.AbstractAotProcessor; import org.springframework.context.support.GenericApplicationContext; import org.springframework.util.FileCopyUtils; @@ -74,6 +76,8 @@ class DockerComposeLifecycleManagerTests { private Set activeProfiles; + private List arguments; + private GenericApplicationContext applicationContext; private TestSpringApplicationShutdownHandlers shutdownHandlers; @@ -123,7 +127,7 @@ void startWhenEnabledFalseDoesNotStart() { @Test void startWhenAotProcessingDoesNotStart() { - withSystemProperty("spring.aot.processing", "true", () -> { + withSystemProperty(AbstractAotProcessor.AOT_PROCESSING, "true", () -> { EventCapturingListener listener = new EventCapturingListener(); this.eventListeners.add(listener); setUpRunningServices(); @@ -181,10 +185,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 +200,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 +215,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 +230,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 +245,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 +262,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 +278,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 +295,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 @@ -357,6 +361,14 @@ void startGetsDockerComposeWithActiveProfiles() { assertThat(this.activeProfiles).containsExactly("my-profile"); } + @Test + void startGetsDockerComposeWithArguments() { + this.properties.getArguments().add("--project-name=test"); + setUpRunningServices(); + this.lifecycleManager.start(); + assertThat(this.arguments).containsExactly("--project-name=test"); + } + @Test void startPublishesEvent() { EventCapturingListener listener = new EventCapturingListener(); @@ -384,6 +396,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); } @@ -486,8 +530,10 @@ protected DockerComposeFile getComposeFile() { } @Override - protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set activeProfiles) { + protected DockerCompose getDockerCompose(DockerComposeFile composeFile, Set activeProfiles, + List arguments) { DockerComposeLifecycleManagerTests.this.activeProfiles = activeProfiles; + DockerComposeLifecycleManagerTests.this.arguments = arguments; return DockerComposeLifecycleManagerTests.this.dockerCompose; } 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..777c08a1033c 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}. @@ -42,7 +47,7 @@ class DockerComposePropertiesTests { void getWhenNoPropertiesReturnsNew() { Binder binder = new Binder(new MapConfigurationPropertySource()); DockerComposeProperties properties = DockerComposeProperties.get(binder); - assertThat(properties.getFile()).isNull(); + assertThat(properties.getFile()).isEmpty(); assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_AND_STOP); assertThat(properties.getHost()).isNull(); assertThat(properties.getStart().getCommand()).isEqualTo(StartCommand.UP); @@ -58,6 +63,7 @@ void getWhenNoPropertiesReturnsNew() { @Test void getWhenPropertiesReturnsBound() { Map source = new LinkedHashMap<>(); + source.put("spring.docker.compose.arguments", "--project-name=test,--progress=auto"); source.put("spring.docker.compose.file", "my-compose.yml"); source.put("spring.docker.compose.lifecycle-management", "start-only"); source.put("spring.docker.compose.host", "myhost"); @@ -71,7 +77,8 @@ void getWhenPropertiesReturnsBound() { source.put("spring.docker.compose.readiness.tcp.read-timeout", "500ms"); Binder binder = new Binder(new MapConfigurationPropertySource(source)); DockerComposeProperties properties = DockerComposeProperties.get(binder); - assertThat(properties.getFile()).isEqualTo(new File("my-compose.yml")); + assertThat(properties.getArguments()).containsExactly("--project-name=test", "--progress=auto"); + assertThat(properties.getFile()).containsExactly(new File("my-compose.yml")); assertThat(properties.getLifecycleManagement()).isEqualTo(LifecycleManagement.START_ONLY); assertThat(properties.getHost()).isEqualTo("myhost"); assertThat(properties.getStart().getCommand()).isEqualTo(StartCommand.START); @@ -84,4 +91,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/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/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/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/clickhouse/ClickHouseEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseEnvironmentTests.java new file mode 100644 index 000000000000..3b0bc26af1fd --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/clickhouse/ClickHouseEnvironmentTests.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.clickhouse; + +import java.util.Collections; +import java.util.Map; + +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 ClickHouseEnvironment}. + * + * @author Stephane Nicoll + */ +class ClickHouseEnvironmentTests { + + @Test + void createWhenNoPasswordThrowsException() { + assertThatIllegalStateException().isThrownBy(() -> new ClickHouseEnvironment(Collections.emptyMap())) + .withMessage("No ClickHouse password found"); + } + + @Test + void getPasswordWhenHasPassword() { + ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("CLICKHOUSE_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + + @Test + void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() { + ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "true")); + assertThat(environment.getPassword()).isEmpty(); + } + + @Test + void getPasswordWhenHasNoPasswordAndAllowEmptyPasswordIsFalse() { + assertThatIllegalStateException() + .isThrownBy(() -> new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "false"))) + .withMessage("No ClickHouse password found"); + } + + @Test + void getUsernameWhenNoUser() { + ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("CLICKHOUSE_PASSWORD", "secret")); + assertThat(environment.getUsername()).isEqualTo("default"); + } + + @Test + void getUsernameWhenHasUser() { + ClickHouseEnvironment environment = new ClickHouseEnvironment( + Map.of("CLICKHOUSE_USER", "me", "CLICKHOUSE_PASSWORD", "secret")); + assertThat(environment.getUsername()).isEqualTo("me"); + } + + @Test + void getDatabaseWhenNoDatabase() { + ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("CLICKHOUSE_PASSWORD", "secret")); + assertThat(environment.getDatabase()).isEqualTo("default"); + } + + @Test + void getDatabaseWhenHasDatabase() { + ClickHouseEnvironment environment = new ClickHouseEnvironment( + Map.of("CLICKHOUSE_DB", "db", "CLICKHOUSE_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/hazelcast/HazelcastEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java new file mode 100644 index 000000000000..a021f47fb4b2 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.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.docker.compose.service.connection.hazelcast; + +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 HazelcastEnvironment}. + * + * @author Dmytro Nosan + */ +class HazelcastEnvironmentTests { + + @Test + void getClusterNameWhenHasNoHzClusterNameSet() { + HazelcastEnvironment environment = new HazelcastEnvironment(Collections.emptyMap()); + assertThat(environment.getClusterName()).isNull(); + } + + @Test + void getClusterNameWhenHzClusterNameSet() { + HazelcastEnvironment environment = new HazelcastEnvironment(Map.of("HZ_CLUSTERNAME", "spring-boot")); + assertThat(environment.getClusterName()).isEqualTo("spring-boot"); + } + +} 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/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/neo4j/Neo4jEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java index 4cbb02d0b608..528682e773e1 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 +29,12 @@ * Tests for {@link Neo4jEnvironment}. * * @author Andy Wilkinson + * @author Scott Frederick */ class Neo4jEnvironmentTests { @Test - void whenNeo4jAuthIsNullThenAuthTokenIsNull() { + void whenNeo4jAuthAndPasswordAreNullThenAuthTokenIsNull() { Neo4jEnvironment environment = new Neo4jEnvironment(Collections.emptyMap()); assertThat(environment.getAuthToken()).isNull(); } @@ -56,4 +57,10 @@ void whenNeo4jAuthIsNeitherNoneNorNeo4jSlashPasswordEnvironmentCreationThrows() .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/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/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-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 09f8ef185d23..c34db6f12835 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -1,7 +1,7 @@ plugins { id "dev.adamko.dokkatoo-html" id "java" - id "org.asciidoctor.jvm.convert" + id "org.antora" id "org.springframework.boot.deployed" id 'org.jetbrains.kotlin.jvm' } @@ -9,21 +9,17 @@ plugins { description = "Spring Boot Docs" configurations { - actuatorApiDocumentation autoConfiguration configurationProperties - gradlePluginDocumentation - mavenPluginDocumentation remoteSpringApplicationExample springApplicationExample testSlices - asciidoctorExtensions { - resolutionStrategy { - eachDependency { dependency -> - // Downgrade SnakeYAML as Asciidoctor fails due to an incompatibility - // in the Pysch gem - if (dependency.requested.group.equals("org.yaml")) { - dependency.useVersion("1.33") + 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) } } } @@ -53,16 +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")) - asciidoctorExtensions(project(path: ":spring-boot-project:spring-boot-testcontainers")) - 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")) @@ -77,7 +63,8 @@ dependencies { 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")) @@ -95,6 +82,7 @@ dependencies { implementation("io.micrometer:micrometer-tracing") implementation("io.micrometer:micrometer-registry-graphite") implementation("io.micrometer:micrometer-registry-jmx") + implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0") implementation("io.projectreactor.netty:reactor-netty-http") implementation("io.undertow:undertow-core") implementation("jakarta.annotation:jakarta.annotation-api") @@ -102,10 +90,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" @@ -124,6 +108,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" @@ -184,8 +172,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")) @@ -194,6 +180,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") @@ -206,8 +196,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) { @@ -215,7 +205,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 @@ -253,33 +245,33 @@ task aggregatedJavadoc(type: Javadoc) { task documentTestSlices(type: org.springframework.boot.build.test.autoconfigure.DocumentTestSlices) { testSlices = configurations.testSlices - outputFile = layout.buildDirectory.file("docs/generated/test-auto-configuration/documented-slices.adoc") + outputFile = layout.buildDirectory.file("generated/docs/test-auto-configuration/documented-slices.adoc") } task documentStarters(type: org.springframework.boot.build.starters.DocumentStarters) { - outputDir = layout.buildDirectory.dir("docs/generated/using/starters/") + outputDir = layout.buildDirectory.dir("generated/docs/using/starters/") } task documentAutoConfigurationClasses(type: org.springframework.boot.build.autoconfigure.DocumentAutoConfigurationClasses) { autoConfiguration = configurations.autoConfiguration - outputDir = layout.buildDirectory.dir("docs/generated/auto-configuration-classes/documented-auto-configuration-classes/") + outputDir = layout.buildDirectory.dir("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 = layout.buildDirectory.file("docs/generated/dependency-versions/documented-coordinates.adoc") + outputFile = layout.buildDirectory.file("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 = layout.buildDirectory.file("docs/generated/dependency-versions/documented-properties.adoc") + outputFile = layout.buildDirectory.file("generated/docs/dependency-versions/documented-properties.adoc") } task documentConfigurationProperties(type: org.springframework.boot.build.context.properties.DocumentConfigurationProperties) { configurationPropertyMetadata = configurations.configurationProperties - outputDir = layout.buildDirectory.dir("docs/generated/") + outputDir = layout.buildDirectory.dir("generated/docs/application-properties") } task documentDevtoolsPropertyDefaults(type: org.springframework.boot.build.devtools.DocumentDevtoolsPropertyDefaults) {} @@ -312,215 +304,105 @@ task runLoggingFormatExample(type: org.springframework.boot.build.docs.Applicati 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 extractMajorMinor = version -> version.split("\\.").take(2).join('.') - def toMajorMinorVersion = version -> { - String formatted = extractMajorMinor(version) + '.x' - return version.endsWith("-SNAPSHOT") ? formatted + "-SNAPSHOT" : formatted - } - def toSpringDataVersion = version -> extractMajorMinor(version) + '.x' - def toAntoraVersion = version -> { - String formatted = extractMajorMinor(version) - 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": toMajorMinorVersion(versionConstraints["org.springframework.amqp:spring-amqp"]), - "spring-batch-version": toMajorMinorVersion(versionConstraints["org.springframework.batch:spring-batch-core"]), - "spring-batch-version-antora": toAntoraVersion(versionConstraints["org.springframework.batch:spring-batch-core"]), - "spring-boot-version": project.version, - "spring-data-cassandra-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-cassandra"]), - "spring-data-commons-version": toSpringDataVersion(versionConstraints["org.springframework.data:spring-data-commons"]), - "spring-data-couchbase-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-couchbase"]), - "spring-data-elasticsearch-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-elasticsearch"]), - "spring-data-jdbc-version": toSpringDataVersion(versionConstraints["org.springframework.data:spring-data-jdbc"]), - "spring-data-jdbc-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-jdbc"]), - "spring-data-jpa-version": toSpringDataVersion(versionConstraints["org.springframework.data:spring-data-jpa"]), - "spring-data-jpa-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-jpa"]), - "spring-data-ldap-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-ldap"]), - "spring-data-mongodb-version": toSpringDataVersion(versionConstraints["org.springframework.data:spring-data-mongodb"]), - "spring-data-neo4j-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-neo4j"]), - "spring-data-r2dbc-version": toSpringDataVersion(versionConstraints["org.springframework.data:spring-data-r2dbc"]), - "spring-data-r2dbc-version-antora": toAntoraVersion(versionConstraints["org.springframework.data:spring-data-r2dbc"]), - "spring-data-rest-version": toSpringDataVersion(versionConstraints["org.springframework.data:spring-data-rest-core"]), - "spring-framework-version": toMajorMinorVersion(versionConstraints["org.springframework:spring-core"]), - "spring-framework-version-antora": toAntoraVersion(versionConstraints["org.springframework:spring-core"]), - "spring-graphql-version-antora": toAntoraVersion(versionConstraints["org.springframework.graphql:spring-graphql"]), - "spring-integration-version-antora": toAntoraVersion(versionConstraints["org.springframework.integration:spring-integration-core"]), - "spring-kafka-version": toMajorMinorVersion(versionConstraints["org.springframework.kafka:spring-kafka"]), - "spring-pulsar-version": toMajorMinorVersion(versionConstraints["org.springframework.pulsar:spring-pulsar"]), - "spring-security-version-antora": toAntoraVersion(versionConstraints["org.springframework.security:spring-security-core"]), - "spring-authorization-server-version-antora": toAntoraVersion(versionConstraints["org.springframework.security:spring-security-oauth2-authorization-server"]), - "spring-webservices-version": toMajorMinorVersion(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(layout.buildDirectory.dir("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(layout.buildDirectory.dir("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(layout.buildDirectory.dir("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..06057b16570a --- /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.4 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 named `spring` (`_spring` for zsh) or put it in your personal or system-wide bash completion initialization. +On a Debian system, the system-wide scripts are in `/shell-completion/` 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 + encodepassword help init shell 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..1ff13213b36b --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc @@ -0,0 +1,1618 @@ +: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.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.customize-whitelabel-error-page[#howto.actuator.customize-whitelabel-error-page] +* 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.controlling-access[#actuator.endpoints.enabling] +* xref:reference:actuator/endpoints.adoc#actuator.endpoints.cors[#actuator.endpoints.cors] +* 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.controlling-access[#actuator.endpoints.enabling] +* 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..8c82cbd552c2 --- /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 at least https://www.java.com[Java 17] and is compatible with versions up to and including Java 23. +{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.4 or later) +|=== + + + +[[getting-started.system-requirements.servlet-containers]] +== Servlet Containers + +Spring Boot supports the following embedded servlet containers: + +|=== +| Name | Servlet Version + +| Tomcat 10.1 (10.1.25 or later) +| 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..21cc3c5acb76 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/application-properties/index.adoc @@ -0,0 +1,48 @@ +[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/actuator.adoc[] + +include::partial$configuration-properties/cache.adoc[] + +include::partial$configuration-properties/core.adoc[] + +include::partial$configuration-properties/data-migration.adoc[] + +include::partial$configuration-properties/data.adoc[] + +include::partial$configuration-properties/devtools.adoc[] + +include::partial$configuration-properties/docker-compose.adoc[] + +include::partial$configuration-properties/integration.adoc[] + +include::partial$configuration-properties/json.adoc[] + +include::partial$configuration-properties/mail.adoc[] + +include::partial$configuration-properties/rsocket.adoc[] + +include::partial$configuration-properties/security.adoc[] + +include::partial$configuration-properties/server.adoc[] + +include::partial$configuration-properties/templating.adoc[] + +include::partial$configuration-properties/testcontainers.adoc[] + +include::partial$configuration-properties/testing.adoc[] + +include::partial$configuration-properties/transaction.adoc[] + +include::partial$configuration-properties/web.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..a1c91e76b4d7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/actuator.adoc @@ -0,0 +1,40 @@ +[[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 javadoc:org.springframework.boot.actuate.autoconfigure.web.server.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.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..59283af1a3ee --- /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 javadoc:{url-spring-framework-javadoc}/org.springframework.context.annotation.Conditional[format=annotation] annotations 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/asciidoc/howto/application.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/application.adoc similarity index 80% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/application.adoc index cb561327a793..fa161f4a6893 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/application.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/application.adoc @@ -1,12 +1,14 @@ [[howto.application]] -== Spring Boot 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`]. +== Create Your Own FailureAnalyzer + +javadoc:org.springframework.boot.diagnostics.FailureAnalyzer[] is a great way to intercept an exception on startup and turn it into a human-readable message, wrapped in a javadoc:org.springframework.boot.diagnostics.FailureAnalysis[]. Spring Boot provides such an analyzer for application-context-related exceptions, JSR-303 validations, and more. You can also create your own. @@ -17,10 +19,10 @@ If, for whatever reason, you cannot handle the exception, return `null` to give `FailureAnalyzer` implementations must be registered in `META-INF/spring.factories`. The following example registers `ProjectConstraintViolationFailureAnalyzer`: -[source,properties,indent=0,subs="verbatim"] +[source,properties] ---- - org.springframework.boot.diagnostics.FailureAnalyzer=\ - com.example.ProjectConstraintViolationFailureAnalyzer +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. @@ -28,22 +30,23 @@ NOTE: If you need access to the `BeanFactory` or the `Environment`, declare them [[howto.application.troubleshoot-auto-configuration]] -=== 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. +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 Javadoc. +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 {spring-boot-autoconfigure-module-code}/web/ServerProperties.java[`ServerProperties`]) and read from there the available external configuration options. +* Look for classes that are `@ConfigurationProperties` (such as javadoc:org.springframework.boot.autoconfigure.web.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. @@ -55,7 +58,8 @@ When reading the code, remember the following rules of thumb: [[howto.application.customize-the-environment-or-application-context]] -=== Customize the Environment or ApplicationContext Before It Starts +== 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: @@ -64,20 +68,20 @@ There is more than one way to register additional customizations: * 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. +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: -[indent=0] +[source] ---- - org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor +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[] +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. @@ -91,14 +95,16 @@ This is too late to configure certain properties such as `+logging.*+` and `+spr [[howto.application.context-hierarchy]] -=== Build an ApplicationContext Hierarchy (Adding a Parent or Root Context) +== 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. +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 +== 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. 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..7da89c464644 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/batch.adoc @@ -0,0 +1,94 @@ +[[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 (for example by retaining the main auto-configured `DataSource`), set the `defaultCandidate` attribute of the `@Bean` annotation to `false`. +To take greater control, add `@EnableBatchProcessing` to one of your `@Configuration` classes or extend `DefaultBatchConfiguration`. +See the API documentation of javadoc:{url-spring-batch-javadoc}/org.springframework.batch.core.configuration.annotation.EnableBatchProcessing[format=annotation] +and javadoc:{url-spring-batch-javadoc}/org.springframework.batch.core.configuration.support.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 batch processing by annotating its `@Bean` method with `@BatchTransactionManager`. +If you do so and want two transaction managers (for example by retaining the auto-configured `PlatformTransactionManager`), set the `defaultCandidate` attribute of the `@Bean` annotation to `false`. + + + +[[howto.batch.specifying-a-task-executor]] +== Specifying a Batch Task Executor + +Similar to xref:batch.adoc#howto.batch.specifying-a-data-source[], you can define a `TaskExecutor` for use in batch processing by annotating its `@Bean` method with `@BatchTaskExecutor`. +If you do so and want two task executors (for example by retaining the auto-configured `TaskExecutor`), set the `defaultCandidate` attribute of the `@Bean` annotation to `false`. + + + +[[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 javadoc:org.springframework.boot.autoconfigure.batch.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..08180ef329cc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc @@ -0,0 +1,339 @@ +[[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.generate-cyclonedx-sbom]] +== Generate a CycloneDX SBOM + +Both Maven and Gradle allow generating a CycloneDX SBOM at project build time. + +For Maven users, the `spring-boot-starter-parent` POM includes a pre-configured plugin to generate the SBOM. +To use it, add the following declaration for the {url-cyclonedx-docs-maven-plugin}[`cyclonedx-maven-plugin`] to your POM: + +[source,xml] +---- + + + + org.cyclonedx + cyclonedx-maven-plugin + + + +---- + +Gradle users can achieve the same result by using the {url-cyclonedx-docs-gradle-plugin}[`cyclonedx-gradle-plugin`] plugin, as shown in the following example: + +[source,gradle] +---- +plugins { + id 'org.cyclonedx.bom' version '1.10.0' +} +---- + + + +[[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..69672185c2fd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/class-data-sharing.adoc @@ -0,0 +1,33 @@ +[[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.dockerfiles]] +== Packaging an Application Using CDS and Dockerfiles + +If you don't want to use Cloud Native Buildpacks, it is also possible to use CDS with a `Dockerfile`. +For more information about that, please see the xref:reference:packaging/container-images/dockerfiles.adoc#packaging.container-images.dockerfiles.cds[Dockerfiles reference documentation]. + + + +[[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..d4a562bc5317 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/data-access.adoc @@ -0,0 +1,402 @@ +[[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 + +To define an additional `DataSource`, an approach that's similar to the previous section can be used. +A key difference is that the `DataSource` `@Bean` must be declared with `defaultCandidate=false`. +This prevents the auto-configured `DataSource` from backing off. + +NOTE: The {url-spring-framework-docs}/core/beans/dependencies/factory-autowire.html#beans-factory-autowire-candidate[Spring Framework reference documentation] describes this feature in more details. + +To allow the additional `DataSource` to be injected where it's needed, also annotate it with `@Qualifier` as shown in the following example: + +include-code::MyAdditionalDataSourceConfiguration[] + +To consume the additional `DataSource`, annotate the injection point with the same `@Qualifier`. + +The auto-configured and additional data sources can be configured as follows: + +[configprops%novalidate,yaml] +---- +spring: + datasource: + url: "jdbc:mysql://localhost/first" + username: "dbuser" + password: "dbpass" + configuration: + maximum-pool-size: 30 +app: + datasource: + url: "jdbc:mysql://localhost/second" + username: "dbuser" + password: "dbpass" + max-total: 30 +---- + +More advanced, implementation-specific, configuration of the auto-configured `DataSource` is available through the `spring.datasource.configuration.*` properties. +You can apply the same concept to the additional `DataSource` as well, as shown in the following example: + +include-code::MyCompleteAdditionalDataSourceConfiguration[] + +The preceding example configures the additional data source with the same logic as Spring Boot would use in auto-configuration. +Note that the `app.datasource.configuration.*` properties provide 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` implementations 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` implementations 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. + +NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost. +Make sure to use the auto-configured `EntityManagerFactoryBuilder` to retain JPA and vendor properties. +This is particularly important if you were relying on `spring.jpa.*` properties for configuring things like the naming strategy or the DDL mode. + + + +[[howto.data-access.use-multiple-entity-managers]] +== Using Multiple EntityManagerFactories + +If you need to use JPA against multiple datasources, you likely need one `EntityManagerFactory` per datasource. +The `LocalContainerEntityManagerFactoryBean` from Spring ORM allows you to configure an `EntityManagerFactory` for your needs. +You can also reuse `JpaProperties` to bind settings for a second `EntityManagerFactory`. +Building upon xref:how-to:data-access.adoc#howto.data-access.configure-two-datasources[the example for configuring a second `DataSource`], a second `EntityManagerFactory` can be defined as shown in the following example: + +include-code::MyAdditionalEntityManagerFactoryConfiguration[] + +The example above creates an `EntityManagerFactory` using the `DataSource` bean qualified with `@Qualifier("second")`. +It scans entities located in the same package as `Order`. +It is possible to map additional JPA properties using the `app.jpa` namespace. +The use of `@Bean(defaultCandidate=false)` allows the `secondJpaProperties` and `secondEntityManagerFactory` beans to be defined without interfering with auto-configured beans of the same type. + +NOTE: The {url-spring-framework-docs}/core/beans/dependencies/factory-autowire.html#beans-factory-autowire-candidate[Spring Framework reference documentation] describes this feature in more details. + +You should provide a similar configuration for any more 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 javadoc:{url-spring-data-rest-javadoc}/org.springframework.data.rest.core.config.RepositoryRestConfiguration[]. +If you need to provide additional customization, you should use a javadoc:{url-spring-data-rest-javadoc}/org.springframework.data.rest.webmvc.config.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 82% 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 7eb1a35a6129..66c28e21dee2 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,7 +31,8 @@ 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`. @@ -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,48 +88,54 @@ 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 -To automatically run Flyway database migrations on startup, add the `org.flywaydb:flyway-core` to your classpath. +=== Execute Flyway Database Migrations on Startup + +To automatically run Flyway database migrations on startup, add the appropriate Flyway module to your classpath. +In-memory and file-based databases are supported by `org.flywaydb:flyway-core`. +Otherwise, a database-specific module is required. +For example, use `org.flywaydb:flyway-database-postgresql` with PostgreSQL and `org.flywaydb:flyway-mysql` with MySQL. +See https://documentation.red-gate.com/flyway/flyway-cli-and-api/supported-databases[the Flyway Documentation] for further details. Typically, migrations are scripts in the form `V__.sql` (with `` an underscore-separated version, such as '`1`' or '`2_1`'). By default, they are in a directory called `classpath:db/migration`, but you can modify that location by setting `spring.flyway.locations`. 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 javadoc:org.springframework.boot.jdbc.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. +javadoc:org.springframework.boot.autoconfigure.flyway.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 javadoc:org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy[]. Flyway supports SQL and Java https://documentation.red-gate.com/fd/callback-concept-184127466.html[callbacks]. To use SQL-based callbacks, place the callback scripts in the `classpath:db/migration` directory. @@ -137,7 +146,7 @@ Beans that implement the deprecated `FlywayCallback` interface can also be detec By default, Flyway autowires the (`@Primary`) `DataSource` in your context and uses that for migrations. If you like to use a different `DataSource`, you can create one and mark its `@Bean` as `@FlywayDataSource`. -If you do so and want two data sources, remember to create another one and mark it as `@Primary`. +If you do so and want two data sources (for example by retaining the main auto-configured `DataSource`), remember to set the `defaultCandidate` attribute of the `@Bean` annotation to `false`. Alternatively, you can use Flyway's native `DataSource` by setting `spring.flyway.[url,user,password]` in external properties. Setting either `spring.flyway.url` or `spring.flyway.user` is sufficient to cause Flyway 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. @@ -147,11 +156,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 +168,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] @@ -174,17 +184,20 @@ In addition to YAML, Liquibase also supports JSON, XML, and SQL change log forma By default, Liquibase autowires the (`@Primary`) `DataSource` in your context and uses that for migrations. If you need to use a different `DataSource`, you can create one and mark its `@Bean` as `@LiquibaseDataSource`. -If you do so and you want two data sources, remember to create another one and mark it as `@Primary`. +If you do so and want two data sources (for example by retaining the main auto-configured `DataSource`), remember to set the `defaultCandidate` attribute of the `@Bean` annotation to `false`. Alternatively, you can use Liquibase's native `DataSource` by setting `spring.liquibase.[driver-class-name,url,user,password]` in external properties. 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 javadoc:org.springframework.boot.autoconfigure.liquibase.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 +206,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 +225,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 +247,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 +257,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,7 +274,8 @@ 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`) 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..bf0b0a12bf61 --- /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 javadoc:org.springframework.boot.cloud.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..303db28c9a70 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/deployment/installing.adoc @@ -0,0 +1,388 @@ +[[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 + +Type=exec +ExecStart=/path/to/java/home/bin/java -jar /var/myapp/myapp.jar +WorkingDirectory=/var/myapp +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target +---- + +IMPORTANT: Remember to change the `Description`, `User`, `Group`, `ExecStart` and `WorkingDirectory` 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 2d73ceb347db..c25ae6348820 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,46 +37,47 @@ 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. 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. @@ -94,13 +96,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: @@ -116,7 +118,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. @@ -126,30 +128,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..eeb5c14c5faf --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/hotswapping.adoc @@ -0,0 +1,77 @@ +[[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. + +NOTE: Template caching for FreeMarker is not supported with WebFlux. + + + +[[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..f9b3747a79b0 --- /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-java-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/asciidoc/howto/security.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/security.adoc similarity index 77% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/security.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/security.adoc index 5f813b9c76fb..3aa2112aa6fd 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/security.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/security.adoc @@ -1,40 +1,44 @@ [[howto.security]] -== 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]. +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 +== 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 +== 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]). +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 +== 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: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - server: - tomcat: - remoteip: - remote-ip-header: "x-forwarded-for" - protocol-header: "x-forwarded-proto" +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. @@ -42,4 +46,4 @@ Alternatively, you can add the `RemoteIpValve` by customizing the `TomcatServlet 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[] +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 78% 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 3368ceeec40d..1a35fee47fad 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. @@ -104,7 +108,7 @@ These features are described in several enums (in Jackson) that map onto propert |=== 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. @@ -122,12 +126,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. @@ -138,12 +143,13 @@ As in normal MVC usage, any `WebMvcConfigurer` beans that you provide can also c 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 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. @@ -151,23 +157,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 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. @@ -178,14 +185,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. @@ -216,6 +225,7 @@ If you add your own, you have to be aware of the order and in which position you The prefix is externalized to `spring.freemarker.prefix`, and the suffix is externalized to `spring.freemarker.suffix`. The default values of the prefix and suffix are empty and '`.ftlh`', respectively. You can override `FreeMarkerViewResolver` by providing a bean of the same name. + FreeMarker variables can be customized by defining a bean of type `FreeMarkerVariablesCustomizer`. * If you use Groovy templates (actually, if `groovy-templates` is on your classpath), you also have a `GroovyMarkupViewResolver` named '`groovyMarkupViewResolver`'. It looks for resources in a loader path by surrounding the view name with a prefix and suffix (externalized to `spring.groovy.template.prefix` and `spring.groovy.template.suffix`). The prefix and suffix have default values of '`classpath:/templates/`' and '`.tpl`', respectively. @@ -228,7 +238,27 @@ 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`] + + + +[[howto.spring-mvc.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. 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..b9b93481284d --- /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 javadoc:org.springframework.boot.autoconfigure.web.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 javadoc:org.springframework.boot.web.server.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 javadoc:org.springframework.boot.web.server.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 javadoc:org.springframework.boot.cloud.CloudPlatform#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..f109f33ca424 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc @@ -0,0 +1,1329 @@ +[[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.controlling-access[control access] to 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 access to it is permitted and it is 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.controlling-access]] +== Controlling Access to Endpoints + +By default, access to all endpoints except for `shutdown` is unrestricted. +To configure the permitted access to an endpoint, use its `management.endpoint..access` property. +The following example allows unrestricted access to the `shutdown` endpoint: + +[configprops,yaml] +---- +management: + endpoint: + shutdown: + access: unrestricted +---- + +If you prefer access to be opt-in rather than opt-out, set the configprop:management.endpoints.access.default[] property to `disabled` and use individual endpoint `access` properties to opt back in. +The following example allows read-only access to the `loggers` endpoint and disables all other endpoints: + +[configprops,yaml] +---- +management: + endpoints: + access: + default: disabled + endpoint: + loggers: + access: read-only +---- + +NOTE: Inaccessible 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.controlling-access.limiting]] +=== Limiting Access + +Application-wide endpoint access can be limited using the configprop:management.endpoints.access.max-permitted[] property. +This property takes precedence over the default access or an individual endpoint's access level. +Set it to `none` to make all endpoints inaccessible. +Set it to `read-only` to only allow read access to endpoints. + +For `@Endpoint`, `@JmxEndpoint`, and `@WebEndpoint`, read access equates to the endpoint methods annotated with `@ReadEndpoint`. +For `@ControllerEndpoint` and `@RestControllerEndpoint`, read access equates to request mappings that can handle `GET` and `HEAD` requests. +For `@ServletEndpoint`, read access equates to `GET` and `HEAD` requests. + + + +[[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 javadoc:org.springframework.boot.actuate.autoconfigure.endpoint.web.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`. +For Kotlin code, please review {url-spring-framework-docs}/languages/kotlin/classes-interfaces.html[the recommendation] of the Spring Framework reference. +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 javadoc:org.springframework.boot.actuate.health.HealthContributorRegistry[] (by default, all javadoc:org.springframework.boot.actuate.health.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` +| javadoc:org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator[] +| Checks that a Cassandra database is up. + +| `couchbase` +| javadoc:org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator[] +| Checks that a Couchbase cluster is up. + +| `db` +| javadoc:org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator[] +| Checks that a connection to `DataSource` can be obtained. + +| `diskspace` +| javadoc:org.springframework.boot.actuate.system.DiskSpaceHealthIndicator[] +| Checks for low disk space. + +| `elasticsearch` +| javadoc:org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator[] +| Checks that an Elasticsearch cluster is up. + +| `hazelcast` +| javadoc:org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator[] +| Checks that a Hazelcast server is up. + +| `jms` +| javadoc:org.springframework.boot.actuate.jms.JmsHealthIndicator[] +| Checks that a JMS broker is up. + +| `ldap` +| javadoc:org.springframework.boot.actuate.ldap.LdapHealthIndicator[] +| Checks that an LDAP server is up. + +| `mail` +| javadoc:org.springframework.boot.actuate.mail.MailHealthIndicator[] +| Checks that a mail server is up. + +| `mongo` +| javadoc:org.springframework.boot.actuate.data.mongo.MongoHealthIndicator[] +| Checks that a Mongo database is up. + +| `neo4j` +| javadoc:org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator[] +| Checks that a Neo4j database is up. + +| `ping` +| javadoc:org.springframework.boot.actuate.health.PingHealthIndicator[] +| Always responds with `UP`. + +| `rabbit` +| javadoc:org.springframework.boot.actuate.amqp.RabbitHealthIndicator[] +| Checks that a Rabbit server is up. + +| `redis` +| javadoc:org.springframework.boot.actuate.data.redis.RedisHealthIndicator[] +| Checks that a Redis server is up. + +| `ssl` +| javadoc:org.springframework.boot.actuate.ssl.SslHealthIndicator[] +| Checks that SSL certificates are ok. +|=== + +TIP: You can disable them all by setting the configprop:management.health.defaults.enabled[] property. + +TIP: The `ssl` `HealthIndicator` has a "warning threshold" property named configprop:management.health.ssl.certificate-validity-warning-threshold[]. +If an SSL certificate will be invalid within the time span defined by this threshold, the `HealthIndicator` will warn you but it will still return HTTP 200 to not disrupt the application. +You can use this threshold to give yourself enough lead time to rotate the soon to be expired certificate. + + + +Additional `HealthIndicators` are available but are not enabled by default: + +[cols="3,4,6"] +|=== +| Key | Name | Description + +| `livenessstate` +| javadoc:org.springframework.boot.actuate.availability.LivenessStateHealthIndicator[] +| Exposes the "`Liveness`" application availability state. + +| `readinessstate` +| javadoc:org.springframework.boot.actuate.availability.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 javadoc:org.springframework.boot.actuate.health.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 javadoc:org.springframework.boot.actuate.health.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 javadoc:org.springframework.boot.actuate.health.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 javadoc:org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry[] (by default, all javadoc:org.springframework.boot.actuate.health.HealthContributor[] and javadoc:org.springframework.boot.actuate.health.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 javadoc:org.springframework.boot.actuate.health.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` +| javadoc:org.springframework.boot.actuate.cassandra.CassandraDriverReactiveHealthIndicator[] +| Checks that a Cassandra database is up. + +| `couchbase` +| javadoc:org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator[] +| Checks that a Couchbase cluster is up. + +| `elasticsearch` +| javadoc:org.springframework.boot.actuate.data.elasticsearch.ElasticsearchReactiveHealthIndicator[] +| Checks that an Elasticsearch cluster is up. + +| `mongo` +| javadoc:org.springframework.boot.actuate.data.mongo.MongoReactiveHealthIndicator[] +| Checks that a Mongo database is up. + +| `neo4j` +| javadoc:org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator[] +| Checks that a Neo4j database is up. + +| `redis` +| javadoc:org.springframework.boot.actuate.data.redis.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 javadoc:org.springframework.boot.actuate.info.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` +| javadoc:org.springframework.boot.actuate.info.BuildInfoContributor[] +| Exposes build information. +| A `META-INF/build-info.properties` resource. + +| `env` +| javadoc:org.springframework.boot.actuate.info.EnvironmentInfoContributor[] +| Exposes any property from the `Environment` whose name starts with `info.`. +| None. + +| `git` +| javadoc:org.springframework.boot.actuate.info.GitInfoContributor[] +| Exposes git information. +| A `git.properties` resource. + +| `java` +| javadoc:org.springframework.boot.actuate.info.JavaInfoContributor[] +| Exposes Java runtime information. +| None. + +| `os` +| javadoc:org.springframework.boot.actuate.info.OsInfoContributor[] +| Exposes Operating System information. +| None. + +| `process` +| javadoc:org.springframework.boot.actuate.info.ProcessInfoContributor[] +| Exposes process information. +| None. + +| `ssl` +| javadoc:org.springframework.boot.actuate.info.SslInfoContributor[] +| Exposes SSL certificate information. +| An xref:features/ssl.adoc#features.ssl.bundles[SSL Bundle] configured. + +|=== + +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. The `ssl` contributor has a prerequisite of having an xref:features/ssl.adoc#features.ssl.bundles[SSL Bundle] configured but it is 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 javadoc:org.springframework.boot.info.JavaInfo[] for more details. + + + +[[actuator.endpoints.info.os-information]] +=== OS Information + +The `info` endpoint publishes information about your Operating System, see javadoc:org.springframework.boot.info.OsInfo[] for more details. + + + +[[actuator.endpoints.info.process-information]] +=== Process Information + +The `info` endpoint publishes information about your process, see javadoc:org.springframework.boot.info.ProcessInfo[] for more details. + + + +[[actuator.endpoints.info.ssl-information]] +=== SSL Information + +The `info` endpoint publishes information about your SSL certificates (that are configured through xref:features/ssl.adoc#features.ssl.bundles[SSL Bundles]), see javadoc:org.springframework.boot.info.SslInfo[] for more details. This endpoint reuses the "warning threshold" property of javadoc:org.springframework.boot.actuate.ssl.SslHealthIndicator[]: if an SSL certificate will be invalid within the time span defined by this threshold, it will trigger a warning. See the `management.health.ssl.certificate-validity-warning-threshold` property. + + + +[[actuator.endpoints.info.writing-custom-info-contributors]] +=== Writing Custom InfoContributors + +To provide custom application information, you can register Spring beans that implement the javadoc:org.springframework.boot.actuate.info.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 `sbom` actuator endpoint will then expose an SBOM called "application", which describes the contents of your application. + +TIP: To automatically generate a CycloneDX SBOM at project build time, please see the xref:how-to:build.adoc#howto.build.generate-cyclonedx-sbom[] section. + + + +[[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 e50027bf668b..4751c26dd064 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`. @@ -10,9 +11,9 @@ If your platform provides a standard `MBeanServer`, Spring Boot uses that and de If all that fails, a new `MBeanServer` is created. NOTE: `spring.jmx.enabled` affects only the management beans provided by Spring. -Enabling management beans provided by other libraries (for example Log4j2 or Quartz) is independent. +Enabling management beans provided by other libraries (for example {url-log4j2-docs}/jmx.html[Log4j2] or {url-quartz-javadoc}/constant-values.html#org.quartz.impl.StdSchedulerFactory.PROP_SCHED_JMX_EXPORT[Quartz]) is independent. -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. @@ -20,7 +21,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`. @@ -30,28 +32,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..7205f387ab03 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/loggers.adoc @@ -0,0 +1,58 @@ +[[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`. + + + +[[actuator.loggers.opentelemetry]] +== OpenTelemetry +By default, logging via OpenTelemetry is not configured. +You have to provide the location of the OpenTelemetry logs endpoint to configure it: + +[configprops,yaml] +---- +management: + otlp: + logging: + endpoint: "https://otlp.example.com:4318/v1/logs" +---- + +NOTE: The OpenTelemetry Logback appender and Log4j appender are not part of Spring Boot. +For more details, see the https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-appender-1.0/library[OpenTelemetry Logback appender] or the https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/log4j/log4j-appender-2.17/library[OpenTelemetry Log4j2 appender] in the https://github.com/open-telemetry/opentelemetry-java-instrumentation[OpenTelemetry Java instrumentation GitHub repository]. + +TIP: You have to configure the appender in your `logback-spring.xml` or `log4j2-spring.xml` configuration to get OpenTelemetry logging working. + +The `OpenTelemetryAppender` for both Logback and Log4j requires access to an `OpenTelemetry` instance to function properly. +This instance must be set programmatically during application startup, which can be done like this: + +include-code::OpenTelemetryAppenderInitializer[] 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..624eabe6a3e7 --- /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[] +- 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 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}/timers.html#_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}/histogram-quantiles.html[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/asciidoc/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc similarity index 79% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc index 772667c34025..7318d12e67ec 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc @@ -1,24 +1,19 @@ [[actuator.observability]] -== 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]. +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[] +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 https://micrometer.io/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]. @@ -28,37 +23,50 @@ To enable it, add the `io.r2dbc:r2dbc-proxy` dependency to your project. +[[actuator.observability.context-propagation]] +== Context Propagation +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]. + + + [[actuator.observability.common-tags]] -=== 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: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - management: - observations: - key-values: - region: "us-east-1" - stack: "prod" +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 +== Preventing Observations If you'd like to prevent some observations from being reported, you can use the configprop:management.observations.enable[] properties: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - management: - observations: - enable: - denied: - prefix: false - another: - denied: - prefix: false +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`. @@ -68,21 +76,20 @@ TIP: If you want to prevent Spring Security from reporting observations, set the 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[] +include-code::MyObservationPredicate[] The preceding example will prevent all observations whose name contains "denied". [[actuator.observability.opentelemetry]] -=== OpenTelemetry Support +== OpenTelemetry Support NOTE: There are several ways to support https://opentelemetry.io/[OpenTelemetry] in your application. You can use the https://opentelemetry.io/docs/zero-code/java/agent/[OpenTelemetry Java Agent] or the https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/[OpenTelemetry Spring Boot Starter], which are supported by the OTel community; the metrics and traces use the semantic conventions defined by OTel libraries. This documentation describes OpenTelemetry as officially supported by the Spring team, using Micrometer and the OTLP exporter; -the metrics and traces use the semantic conventions described in the Spring projects documentation, such as {spring-framework-docs}/integration/observability.html[Spring Framework]. - +the metrics and traces use the semantic conventions described in the Spring projects documentation, such as {url-spring-framework-docs}/integration/observability.html[Spring Framework]. Spring Boot's actuator module includes basic support for OpenTelemetry. @@ -92,13 +99,14 @@ The attributes of the auto-configured `Resource` can be configured via the confi 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 <>. +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 +== 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 {micrometer-concepts-docs}#_the_timed_annotation[Micrometer] and {micrometer-tracing-docs}/api.html#_aspect_oriented_programming[Micrometer Tracing] reference docs. +This feature is supported Micrometer directly. Please refer to the {url-micrometer-docs-concepts}/timers.html#_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/asciidoc/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc similarity index 81% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc index 659e150cdfad..7e8244e38ea1 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc @@ -1,13 +1,15 @@ [[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. += Tracing -TIP: To learn more about Micrometer Tracing capabilities, see its https://micrometer.io/docs/tracing[reference documentation]. +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 +== 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] @@ -16,14 +18,15 @@ Spring Boot ships auto-configuration for the following tracers: [[actuator.micrometer-tracing.getting-started]] -=== 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. +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[] +include-code::MyApplication[] NOTE: There's an added logger statement in the `home()` method, which will be important later. @@ -31,16 +34,16 @@ 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. +* `io.opentelemetry:opentelemetry-exporter-zipkin` - reports {url-micrometer-tracing-docs}/glossary[traces] to Zipkin. Add the following application properties: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - management: - tracing: - sampling: - probability: 1.0 +management: + tracing: + sampling: + probability: 1.0 ---- By default, Spring Boot samples only 10% of requests to prevent overwhelming the trace backend. @@ -54,9 +57,9 @@ 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] +[source] ---- - Hello World! +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. @@ -68,7 +71,8 @@ Press the "Show" button to see the details of that trace. [[actuator.micrometer-tracing.logging]] -=== Logging Correlation IDs +== 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. @@ -78,29 +82,34 @@ For example, if Micrometer Tracing has added an MDC `traceId` of `803B448A0489F8 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: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - logging: - pattern: - correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] " - include-application-name: false +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. +TIP: Correlation IDs rely on context propagation. +Please read xref:reference:actuator/observability.adoc#actuator.observability.context-propagation[this documentation for more details]. + [[actuator.micrometer-tracing.propagating-traces]] -=== Propagating Traces -To automatically propagate traces over the network, use the auto-configured <>, <> or <> to construct the client. +== 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 +== 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. @@ -108,7 +117,8 @@ All tracer implementations need the `org.springframework.boot:spring-boot-starte [[actuator.micrometer-tracing.tracer-implementations.otel-zipkin]] -==== OpenTelemetry With 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. @@ -119,7 +129,8 @@ Use the `management.zipkin.tracing.*` configuration properties to configure repo [[actuator.micrometer-tracing.tracer-implementations.otel-wavefront]] -==== OpenTelemetry With 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. @@ -130,7 +141,8 @@ Use the `management.wavefront.*` configuration properties to configure reporting [[actuator.micrometer-tracing.tracer-implementations.otel-otlp]] -==== OpenTelemetry With 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. @@ -141,20 +153,20 @@ Use the `management.otlp.tracing.*` configuration properties to configure report [[actuator.micrometer-tracing.tracer-implementations.brave-zipkin]] -==== OpenZipkin Brave With 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 +=== 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. @@ -165,28 +177,32 @@ Use the `management.wavefront.*` configuration properties to configure reporting [[actuator.micrometer-tracing.micrometer-observation]] -=== Integration with 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 +== Creating Custom Spans + You can create your own spans by starting an observation. For this, inject `ObservationRegistry` into your component: -include::code:CustomObservation[] +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. +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 +== Baggage + You can create baggage with the `Tracer` API: -include::code:CreatingBaggage[] +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. @@ -200,7 +216,7 @@ For the example above, setting this property to `baggage1` results in an MDC ent [[actuator.micrometer-tracing.tests]] -=== Tests +== Tests Tracing components which are reporting data are not auto-configured when using `@SpringBootTest`. -See <> for more details. +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..ae21503f4d99 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc @@ -0,0 +1,759 @@ +[[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-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" +---- + +You can also specify the url of the Redis server directly. +When setting the url, the host, port, username and password properties are ignored. +This is shown in the following example: + +[configprops,yaml] +---- +spring: + data: + redis: + url: "redis://user:secret@localhost:6379" + database: 0 +---- + + +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 javadoc:{url-spring-data-mongodb-javadoc}/org.springframework.data.mongodb.core.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 javadoc:{url-spring-data-mongodb-javadoc}/org.springframework.data.mongodb.core.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 {url-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://docs.couchbase.com/dotnet-sdk/current/howtos/managing-connections.html[connection string] and credentials for authentication. Basic authentication with username and password can be configured as shown in the following example: + +[configprops,yaml] +---- +spring: + couchbase: + connection-string: "couchbase://192.168.1.123" + username: "user" + password: "secret" +---- + +https://docs.couchbase.com/server/current/manage/manage-security/configure-client-certificates.html[Client certificates] can be used for authentication instead of username and password. +The location and password for a Java KeyStore containing client certificates can be configured as shown in the following example: + +[configprops,yaml] +---- +spring: + couchbase: + connection-string: "couchbase://192.168.1.123" + env: + ssl: + enabled: true + authentication: + jks: + location: "classpath:client.p12" + password: "secret" +---- + +PEM-encoded certificates and a private key can be configured as shown in the following example: + +[configprops,yaml] +---- +spring: + couchbase: + connection-string: "couchbase://192.168.1.123" + env: + ssl: + enabled: true + authentication: + pem: + certificates: "classpath:client.crt" + private-key: "classpath:client.key" +---- + +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. + +TIP: For complete details of Spring Data LDAP, see the {url-spring-data-ldap-docs}[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..01fc6f98db0b --- /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 javadoc:org.springframework.boot.autoconfigure.jdbc.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 javadoc:{url-spring-data-jpa-javadoc}/org.springframework.data.jpa.repository.Query[] annotation. + +Spring Data repositories usually extend from the javadoc:{url-spring-data-commons-javadoc}/org.springframework.data.repository.Repository[] or javadoc:{url-spring-data-commons-javadoc}/org.springframework.data.repository.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 javadoc:{url-spring-framework-javadoc}/org.springframework.orm.jpa.support.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 javadoc:{url-spring-data-r2dbc-javadoc}/org.springframework.data.r2dbc.repository.Query[format=annotation] annotation. + +Spring Data repositories usually extend from the javadoc:{url-spring-data-commons-javadoc}/org.springframework.data.repository.Repository[] or javadoc:{url-spring-data-commons-javadoc}/org.springframework.data.repository.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..a43bb6489847 --- /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..9d02006152be --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc @@ -0,0 +1,410 @@ +[[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" + +| `HazelcastConnectionDetails` +| Containers named "hazelcast/hazelcast". + +| `JdbcConnectionDetails` +| Containers named "clickhouse/clickhouse-server", "bitnami/clickhouse", "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" + +| `OtlpLoggingConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib", "grafana/otel-lgtm" + +| `OtlpMetricsConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib", "grafana/otel-lgtm" + +| `OtlpTracingConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib", "grafana/otel-lgtm" + +| `PulsarConnectionDetails` +| Containers named "apachepulsar/pulsar" + +| `R2dbcConnectionDetails` +| Containers named "clickhouse/clickhouse-server", "bitnami/clickhouse", "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, define an additional `DynamicPropertyRegistrar` bean. +The registrar should be defined using a `@Bean` method that injects the container from which the properties will be sourced as a parameter. +This arrangement ensures that container has been started before the properties are used. + +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 77% 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 e00eef8dd754..6ab3290e11de 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 javadoc:org.springframework.boot.autoconfigure.AutoConfiguration[format=annotation] annotation or the dedicated javadoc:org.springframework.boot.autoconfigure.AutoConfigureBefore[format=annotation] and javadoc:org.springframework.boot.autoconfigure.AutoConfigureAfter[format=annotation] 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`. @@ -50,25 +52,47 @@ The order in which those beans are subsequently created is unaffected and is det +[[features.developing-auto-configuration.locating-auto-configuration-candidates.deprecating]] +=== Deprecating and Replacing Auto-configuration Classes + +You may need to occasionally deprecate auto-configuration classes and offer an alternative. +For example, you may want to change the package name where your auto-configuration class resides. + +Since auto-configuration classes may be referenced in `before`/`after` ordering and `excludes`, you'll need to add an additional file that tells Spring Boot how to deal with replacements. +To define replacements, create a `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements` file indicating the link between the old class and the new one. + +For example: + +[source] +---- +com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration +---- + +NOTE: The `AutoConfiguration.imports` file should also be updated to _only_ reference the replacement class. + + + [[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 +101,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 +133,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 +145,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 +165,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 +175,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 +186,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 +194,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 +249,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 +262,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 +285,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 +305,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 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 c0bfa44904c6..2e4ca3b32dc4 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. +. javadoc:{url-spring-framework-javadoc}/org.springframework.context.annotation.PropertySource[format=annotation] 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 javadoc:org.springframework.boot.test.context.SpringBootTest[format=annotation] and the xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[test annotations for testing a particular slice of your application]. +. javadoc:{url-spring-framework-javadoc}/org.springframework.test.context.DynamicPropertySource[format=annotation] annotations in your tests. +. javadoc:{url-spring-framework-javadoc}/org.springframework.test.context.TestPropertySource[format=annotation] 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: @@ -693,6 +713,8 @@ The preceding POJO defines the following properties: * `my.service.security.password`. * `my.service.security.roles`, with a collection of `String` that defaults to `USER`. +TIP: To use a reserved keyword in the name of a property, such as `my.service.import`, use the `@Name` annotation on the property's field. + NOTE: The properties that map to `@ConfigurationProperties` classes available in Spring Boot, which are configured through properties files, YAML files, environment variables, and other mechanisms, are public API but the accessors (getters/setters) of the class itself are not meant to be used directly. [NOTE] @@ -717,10 +739,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,7 +760,7 @@ 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`) @@ -754,7 +777,8 @@ TIP: To use a reserved keyword in the name of a property, such as `my.service.im [[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. @@ -762,15 +786,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] ==== @@ -787,51 +811,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: @@ -869,8 +896,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 @@ -882,29 +909,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. @@ -921,7 +939,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. @@ -945,13 +964,14 @@ Support for binding from environment variables is applied to the `systemEnvironm [[features.external-config.typesafe-configuration-properties.relaxed-binding.maps-from-environment-variables]] -===== Binding Maps From Environment Variables +==== Binding Maps From Environment Variables + When Spring Boot binds an environment variable to a property class, it lowercases the environment variable name before binding. Most of the time this detail isn't important, except when binding to `Map` properties. The keys in the `Map` are always in lowercase, as seen in the following example: -include::code:MyMapsProperties[] +include-code::MyMapsProperties[] When setting `MY_PROPS_VALUES_KEY=value`, the `values` `Map` contains a `{"key"="value"}` entry. @@ -961,37 +981,39 @@ When setting `MY_PROPS_VALUES_KEY=VALUE`, the `values` `Map` contains a `{"key"= [[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. @@ -1001,22 +1023,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`). @@ -1026,29 +1048,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`). @@ -1059,7 +1081,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`). @@ -1070,17 +1093,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`. @@ -1100,7 +1124,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. @@ -1109,12 +1133,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: @@ -1129,7 +1154,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: @@ -1138,7 +1164,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`. @@ -1156,7 +1182,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. @@ -1164,19 +1190,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`. @@ -1185,12 +1212,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`: @@ -1198,11 +1226,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 @@ -1215,7 +1243,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. @@ -1224,6 +1252,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..3177f0d5fc34 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/internationalization.adoc @@ -0,0 +1,25 @@ +[[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" + common-messages: "classpath:my-common-messages.properties" + fallback-to-system-locale: false +---- + +TIP: The configprop:spring.messages.basename[] property supports a list of locations, either a package qualifier or a resource resolved from the classpath root. +The configprop:spring.messages.common-messages[] property supports a list of property file resources. + +See javadoc:org.springframework.boot.autoconfigure.context.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..9bcbed72196c --- /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 javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] base classes that provide useful alternatives to the standard Jackson versions when serializing objects. +See javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.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 812ee7a1e740..e2f0e41f35e1 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,10 @@ TIP: `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` dependency is provided b [[features.kotlin.configuration-properties]] -=== @ConfigurationProperties -`@ConfigurationProperties` when used in combination with <> supports data 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 data 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( @@ -129,30 +135,32 @@ Due to the limitations of their interoperability with Java, support for value cl In particular, relying upon a value class's default value will not work with configuration property binding. In such cases, a data class should be used instead. -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] @@ -167,7 +175,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 xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.maps-from-environment-variables[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 JSON formats out of the box: + +* xref:#features.logging.structured.ecs[Elastic Common Schema (ECS)] +* xref:#features.logging.structured.gelf[Graylog Extended Log Format (GELF)] +* 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. + +The `service` values can be customized using `logging.structured.ecs.service` properties: + +[configprops,yaml] +---- +logging: + structured: + ecs: + service: + name: MyService + version: 1.0 + environment: Production + node-name: Primary +---- + +NOTE: configprop:logging.structured.ecs.service.name[] will default to configprop:spring.application.name[] if not specified. + +NOTE: configprop:logging.structured.ecs.service.version[] will default to configprop:spring.application.version[] if not specified. + + + +[[features.logging.structured.gelf]] +=== Graylog Extended Log Format (GELF) + +https://go2docs.graylog.org/current/getting_in_log_data/gelf.html[Graylog Extended Log Format] is a JSON based logging format for the Graylog log analytics platform. + +To enable the Graylog Extended Log Format, set the appropriate `format` property to `gelf`: + +[configprops,yaml] +---- +logging: + structured: + format: + console: gelf + file: gelf +---- + +A log line looks like this: + +[source,json] +---- +{"version":"1.1","short_message":"No active profile set, falling back to 1 default profile: \"default\"","timestamp":1725958035.857,"level":6,"_level_name":"INFO","_process_pid":47649,"_process_thread_name":"main","_log_logger":"org.example.Application"} +---- + +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. + +Several fields can be customized using `logging.structured.gelf` properties: + +[configprops,yaml] +---- +logging: + structured: + gelf: + host: MyService + service: + version: 1.0 +---- + +NOTE: configprop:logging.structured.gelf.host[] will default to configprop:spring.application.name[] if not specified. + +NOTE: configprop:logging.structured.gelf.service.version[] will default to configprop:spring.application.version[] if not specified. + + + +[[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.customizing-json]] +=== Customizing Structured Logging JSON + +Spring Boot attempts to pick sensible defaults for the JSON names and values output for structured logging. +Sometimes, however, you may want to make small adjustments to the JSON for your own needs. +For example, it's possible that you might want to change some of the names to match the expectations of your log ingestion system. +You might also want to filter out certain members since you don't find them useful. + +The following properties allow you to change the way that structured logging JSON is written: + +|=== +| Property | Description + +| configprop:logging.structured.json.include[] & configprop:logging.structured.json.exclude[] +| Filters specific paths from the JSON + +| configprop:logging.structured.json.rename[] +| Renames a specific member in the JSON + +| configprop:logging.structured.json.add[] +| Adds additional members to the JSON +|=== + +For example, the following will exclude `log.level`, rename `process.id` to `procid` and add a fixed `corpname` field: + +[configprops,yaml] +---- +logging: + structured: + json: + exclude: log.level + rename: + process.id: procid + add: + corpname: mycorp +---- + +TIP: For more advanced customizations, you can write your own class that implements the javadoc:org.springframework.boot.logging.structured.StructureLoggingJsonMembersCustomizer[] interface and declare it using the configprop:logging.structured.json.customizer[] property. +You can also declare implementations by listing them in a `META-INF/spring.factories` file. + + + +[[features.logging.structured.other-formats]] +=== Supporting Other 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 javadoc:org.springframework.boot.logging.structured.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.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 Log4j2 properties named `applicationName` and `applicationGroup` that read `spring.application.name` and `spring.application.group` from the Spring `Environment`: + +[source,xml] +---- + + ${spring:spring.application.name} + ${spring:spring.application.group} + +---- + +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/systemproperties.html[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/antora/modules/reference/pages/features/profiles.adoc similarity index 78% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/profiles.adoc index 89837251d1b7..5a26c5c7a4a9 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/profiles.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/profiles.adoc @@ -1,9 +1,10 @@ [[features.profiles]] -== 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[] +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. @@ -12,11 +13,11 @@ You can use a configprop:spring.profiles.active[] `Environment` property to spec 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] +[configprops,yaml] ---- - spring: - profiles: - active: "dev,hsqldb" +spring: + profiles: + active: "dev,hsqldb" ---- You could also specify it on the command line by using the following switch: `--spring.profiles.active=dev,hsqldb`. @@ -24,11 +25,11 @@ You could also specify it on the command line by using the following switch: `-- 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] +[configprops,yaml] ---- - spring: - profiles: - default: "none" +spring: + profiles: + default: "none" ---- `spring.profiles.active` and `spring.profiles.default` can only be used in non-profile-specific documents. @@ -36,54 +37,56 @@ This means they cannot be included in xref:features/external-config.adoc#feature For example, the second document configuration is invalid: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - # this document is valid - spring: - profiles: - active: "prod" - --- - # this document is invalid - spring: - config: - activate: - on-profile: "prod" - profiles: - active: "metrics" +# 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 +== 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]. +See the `setAdditionalProfiles()` method in javadoc:org.springframework.boot.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] +[configprops,yaml] ---- - spring: - profiles: - include: - - "common" - - "local" +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 <> can also be used to add active profiles if a given profile is active. +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 +== 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. @@ -92,14 +95,14 @@ A profile group allows you to define a logical name for a related group of profi For example, we can create a `production` group that consists of our `proddb` and `prodmq` profiles. -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- - spring: - profiles: - group: - production: - - "proddb" - - "prodmq" +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. @@ -109,13 +112,15 @@ This means it cannot be included in xref:features/external-config.adoc#features. [[features.profiles.programmatically-setting-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 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. +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 82% 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 f617c67e64a7..d032eac7a0e8 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 javadoc:org.springframework.boot.ansi.AnsiPropertySource[] for details. | `${application.title}` | The title of your application, as declared in `MANIFEST.MF`. @@ -140,38 +144,41 @@ This will initialize the `application.*` banner properties before building the c [[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 javadoc:org.springframework.boot.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 javadoc:org.springframework.boot.builder.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 javadoc:{url-spring-framework-javadoc}/org.springframework.context.event.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 javadoc:org.springframework.boot.admin.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,35 +369,37 @@ 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 javadoc:{url-spring-framework-javadoc}/org.springframework.core.metrics.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 +== 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]. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/ssl.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc similarity index 79% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/ssl.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc index 01afb25affa8..23fe8531b2f5 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/ssl.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/ssl.adoc @@ -1,18 +1,20 @@ [[features.ssl]] -== 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 +== 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] +[configprops,yaml] ---- spring: ssl: @@ -29,7 +31,7 @@ When used to secure an embedded web server, a `keystore` is typically configured 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] +[configprops,yaml] ---- spring: ssl: @@ -41,20 +43,21 @@ When used to secure a client-side connection, a `truststore` is typically config password: "secret" ---- -See {spring-boot-autoconfigure-module-code}/ssl/JksSslBundleProperties.java[JksSslBundleProperties] for the full set of supported properties. +See javadoc:org.springframework.boot.autoconfigure.ssl.JksSslBundleProperties[] for the full set of supported properties. -NOTE: If you're using environment variables to configure the bundle, the name of the bundle is <>. +NOTE: If you're using environment variables to configure the bundle, the name of the bundle is xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.maps-from-environment-variables[always converted to lowercase]. [[features.ssl.pem]] -=== Configuring SSL With PEM-encoded Certificates +== 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] +[configprops,yaml] ---- spring: ssl: @@ -68,7 +71,7 @@ When used to secure an embedded web server, a `keystore` is typically configured 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] +[configprops,yaml] ---- spring: ssl: @@ -82,11 +85,11 @@ When used to secure a client-side connection, a `truststore` is typically config [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. +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: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- spring: ssl: @@ -106,21 +109,23 @@ The following example shows how a truststore certificate can be defined: ---- ==== -See {spring-boot-autoconfigure-module-code}/ssl/PemSslBundleProperties.java[PemSslBundleProperties] for the full set of supported properties. +See javadoc:org.springframework.boot.autoconfigure.ssl.PemSslBundleProperties[] for the full set of supported properties. -NOTE: If you're using environment variables to configure the bundle, the name of the bundle is <>. +NOTE: If you're using environment variables to configure the bundle, the name of the bundle is xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding.maps-from-environment-variables[always converted to lowercase]. [[features.ssl.applying]] -=== Applying SSL Bundles +== 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. +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 +== 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. @@ -134,12 +139,13 @@ In addition, the `SslBundle` provides details about the key being used, the prot The following example shows retrieving an `SslBundle` and using it to create an `SSLContext`: -include::code:MyComponent[] +include-code::MyComponent[] [[features.ssl.reloading]] -=== Reloading SSL bundles +== 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: @@ -149,7 +155,7 @@ Currently the following components are compatible: To enable reloading, you need to opt-in via a configuration property as shown in this example: -[source,yaml,indent=0,subs="verbatim",configblocks] +[configprops,yaml] ---- spring: ssl: 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/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc similarity index 88% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc index f238c9b96934..1acc79847694 100644 --- 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/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc @@ -1,5 +1,6 @@ [[features.task-execution-and-scheduling]] -== 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. @@ -22,15 +23,15 @@ The auto-configured `ThreadPoolTaskExecutorBuilder` allows you to easily create 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: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - task: - execution: - pool: - max-size: 16 - queue-capacity: 100 - keep-alive: "10s" +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. @@ -44,14 +45,14 @@ 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: -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +[configprops,yaml] ---- - spring: - task: - scheduling: - thread-name-prefix: "scheduling-" - pool: - size: 2 +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. 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 76% 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..888b5718574c 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,64 +1,66 @@ [[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. +Before invoking `computePiDecimal`, the abstraction looks for an entry in the `piDecimals` cache that matches the `precision` argument. If an entry is found, the content in the cache is immediately returned to the caller, and the method is not invoked. Otherwise, the method is invoked, and the cache is updated before returning the value. 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 javadoc:{url-spring-framework-javadoc}/org.springframework.cache.annotation.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..185e390e1dae --- /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 javadoc:org.springframework.boot.autoconfigure.mail.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 78% 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..2edcd9ac0a8f 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[] 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[] For consistency, the `jmsConnectionFactory` bean is also provided by using the bean alias `xaJmsConnectionFactory`: -include::code:xa/MyBean[tag=*] +include-code::xa/MyBean[] [[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 javadoc:org.springframework.boot.jms.XAConnectionFactoryWrapper[] and javadoc:org.springframework.boot.jdbc.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..e3aa4d56f995 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc @@ -0,0 +1,242 @@ +[[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. + +TIP: You can also change the xref:io/rest-client.adoc#io.rest-client.clienthttprequestfactory.configuration[global HTTP client configuration]. + + + +[[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 `ClientHttpRequestFactoryBuilder`: + +include-code::settings/MyService[] + + + +[[io.rest-client.resttemplate]] +== RestTemplate + +Spring Framework's javadoc:{url-spring-framework-javadoc}/org.springframework.web.client.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. + +TIP: You can also change the xref:io/rest-client.adoc#io.rest-client.clienthttprequestfactory.configuration[global HTTP client configuration]. + + + +[[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 +. Reactor Netty HttpClient +. JDK client (`java.net.http.HttpClient`) +. Simple JDK client (`java.net.HttpURLConnection`) + +If multiple clients are available on the classpath, and not global configuration is provided, the most preferred client will be used. + + + +[[io.rest-client.clienthttprequestfactory.configuration]] +=== Global HTTP Client Configuration + +If the the auto-detected HTTP client does not meet your needs, you can use the configprop:spring.http.client.factory[] property to pick a specific factory. +For example, if you have Apache HttpClient on your classpath, but you prefer Jetty's `HttpClient` you can add use the following: + +[configprops,yaml] +---- +spring: + http: + client: + factory: jetty +---- + +You can also set properties to change defaults that will be applied to all clients. +For example, you may want to change timeouts and if redirects are followed: + +[configprops,yaml] +---- +spring: + http: + client: + connect-timeout: 2s + read-timeout: 1s + redirects: dont-follow +---- + +For more complex customizations, you can declare your own `ClientHttpRequestFactoryBuilder` bean which will cause auto-configuration to back off. +This can be useful when you need to customize some of the internals of the underlying HTTP library. + +For example, the following will use a JDK client configured with a specific `java.net.ProxySelector`: + +include-code::MyClientHttpConfiguration[] + 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..80219b3dd6b4 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/webservices.adoc @@ -0,0 +1,37 @@ +[[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 for an individual builder as follows: + +include-code::MyWebServiceTemplateConfiguration[] + +TIP: You can also change the xref:io/rest-client.adoc#io.rest-client.clienthttprequestfactory.configuration[global HTTP client configuration] used if not specific template customization code is applied. 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..41de38e042ba 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 javadoc:org.springframework.boot.autoconfigure.amqp.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: javadoc:{url-spring-amqp-javadoc}/org.springframework.amqp.rabbit.core.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 javadoc:{url-spring-amqp-javadoc}/org.springframework.amqp.rabbit.annotation.EnableRabbit[format=annotation] 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..0b7910318b92 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/jms.adoc @@ -0,0 +1,191 @@ +[[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`. +If the broker is present, an embedded broker is automatically started and configured (provided no broker URL is specified through configuration and the embedded broker is not disabled in the configuration). + +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. +Adding `org.apache.activemq:activemq-broker` to your application lets you use the embedded broker. + +ActiveMQ "Classic" configuration is controlled by external configuration properties in `+spring.activemq.*+`. + +If `activemq-broker` is on the classpath, ActiveMQ "Classic" is auto-configured to use the https://activemq.apache.org/vm-transport-reference.html[VM transport], which starts a broker embedded in the same JVM instance. + +You can disable the embedded broker by configuring the configprop:spring.activemq.embedded.enabled[] property, as shown in the following example: + +[configprops,yaml] +---- +spring: + activemq: + embedded: + enabled: false +---- + +The embedded broker will also be disabled if you configure the broker URL, as shown in the following example: + +[configprops,yaml] +---- +spring: + activemq: + broker-url: "tcp://192.168.1.210:9876" + user: "admin" + password: "secret" +---- + +If you want to take full control over the embedded broker, see https://activemq.apache.org/how-do-i-embed-a-broker-inside-a-connection.html[the ActiveMQ "Classic" documentation] for further information. + +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 javadoc:org.springframework.boot.autoconfigure.jms.activemq.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 javadoc:org.springframework.boot.autoconfigure.jms.artemis.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: javadoc:{url-spring-framework-javadoc}/org.springframework.jms.core.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 javadoc:{url-spring-framework-javadoc}/org.springframework.jms.annotation.EnableJms[format=annotation] 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..2946770defe6 --- /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 javadoc:org.springframework.boot.autoconfigure.kafka.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..f758ba85eb58 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/pulsar.adoc @@ -0,0 +1,245 @@ +[[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 configuration of the consumer factory, 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. + +If you need more control over the actual container factory configuration, consider registering one or more `PulsarContainerFactoryCustomizer>` beans. + +[[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 configuration of the consumer factory, 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. + +If you need more control over the actual container factory configuration, consider registering one or more `PulsarContainerFactoryCustomizer>` beans. + +[[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 configuration of the reader factory, 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. + +If you need more control over the actual container factory configuration, consider registering one or more `PulsarContainerFactoryCustomizer>` beans. + + +[[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..3d5c7d3327cb --- /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 javadoc:org.springframework.boot.autoconfigure.integration.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..ec1e817612a8 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/container-images/dockerfiles.adoc @@ -0,0 +1,77 @@ +[[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] +---- +include::reference:partial$dockerfile[] +# Start the application jar - this is not the uber jar used by the builder +# This jar only contains application code and references to the extracted jar files +# This layout is efficient to start up and CDS friendly +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. + + + +[[packaging.container-images.dockerfiles.cds]] +== CDS + +If you want to additionally enable xref:reference:packaging/class-data-sharing.adoc[CDS], you can use this `Dockerfile`: +[source,dockerfile] +---- +include::reference:partial$dockerfile[] +# Execute the CDS training run +RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar +# Start the application jar with CDS enabled - this is not the uber jar used by the builder +# This jar only contains application code and references to the extracted jar files +# This layout is efficient to start up and CDS friendly +ENTRYPOINT ["java", "-XX:SharedArchiveFile=application.jsa", "-jar", "application.jar"] +---- + +This is mostly the same as the above `Dockerfile`. +As the last steps, it creates the CDS archive by doing a training run and passes the CDS parameter to `java -jar`. + 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..b3f9a2974613 --- /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-java-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..145b0983b982 --- /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 javadoc:org.springframework.boot.context.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.html#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.html[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 javadoc:org.springframework.boot.test.autoconfigure.orm.jpa.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..04693495b8d5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc @@ -0,0 +1,133 @@ +[[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.kafka.KafkaContainer`, `org.testcontainers.kafka.ConfluentKafkaContainer` or `RedpandaContainer` + +| `LiquibaseConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `MongoConnectionDetails` +| Containers of type `MongoDBContainer` + +| `Neo4jConnectionDetails` +| Containers of type `Neo4jContainer` + +| `OtlpLoggingConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" or of type `LgtmStackContainer` + +| `OtlpMetricsConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" or of type `LgtmStackContainer` + +| `OtlpTracingConnectionDetails` +| Containers named "otel/opentelemetry-collector-contrib" or of type `LgtmStackContainer` + +| `PulsarConnectionDetails` +| Containers of type `PulsarContainer` + +| `R2dbcConnectionDetails` +| Containers of type `ClickHouseContainer`, `MariaDBContainer`, `MSSQLServerContainer`, `MySQLContainer`, `OracleContainer`, or `PostgreSQLContainer` + +| `RabbitConnectionDetails` +| Containers of type `RabbitMQContainer` + +| `RedisConnectionDetails` +| Containers of type `com.redis.testcontainers.RedisContainer` or `com.redis.testcontainers.RedisStackContainer`, or 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 such as `Neo4jContainer` or `RabbitMQContainer`. +This stops working if you're using `GenericContainer`, for example 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 79% 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..db9540af56b6 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,14 +236,14 @@ 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. The same is true if you use `mvn spring-boot:run` or `gradle bootRun`: the project containing your `@SpringBootApplication` is loaded with the "`restart`" classloader, and everything else with the "`base`" classloader. The classpath is printed on the console when you start the app, which can help to identify any problematic entries. -Classes used reflectively, especially annotations, can be loaded into the parent (fixed) classloader on startup before the application classes which uses them, and this might lead to them not being detected by Spring in the application. +Classes used reflectively, especially annotations, can be loaded into the parent (fixed) classloader on startup before the application classes which use them, and this might lead to them not being detected by Spring in the application. You can instruct Spring Boot to load parts of your project with a different classloader by creating a `META-INF/spring-devtools.properties` file. The `spring-devtools.properties` file can contain properties prefixed with `restart.exclude` and `restart.include`. @@ -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 + +javadoc:org.springframework.boot.devtools.filewatch.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..ba58fafafcfd --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/graceful-shutdown.adoc @@ -0,0 +1,45 @@ +[[web.graceful-shutdown]] += Graceful Shutdown + +Graceful shutdown is enabled by default 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. + +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: Shutdown in your IDE may be immediate rather than graceful if it does not send a proper `SIGTERM` signal. +See the documentation of your IDE for more details. + + + +[[web.graceful-shutdown.rejecting-requests-during-the-grace-period]] +== Rejecting Requests During the Grace Period + +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 more about the specific method used with your web server, see the `shutDownGracefully` API documentation for javadoc:org.springframework.boot.web.embedded.tomcat.TomcatWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[], javadoc:org.springframework.boot.web.embedded.netty.NettyWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[], javadoc:org.springframework.boot.web.embedded.jetty.JettyWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[] or javadoc:org.springframework.boot.web.embedded.undertow.UndertowWebServer#shutDownGracefully(org.springframework.boot.web.server.GracefulShutdownCallback)[]. + +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. + + + +[[web.graceful-shutdown.disabling-graceful-shutdown]] +== Disabling Graceful Shutdown + +To disable graceful shutdown, configure the configprop:server.shutdown[] property, as shown in the following example: + +[configprops,yaml] +---- +server: + shutdown: "immediate" +---- 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/asciidoc/web/reactive.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc similarity index 77% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/reactive.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc index fd2caf9e652a..553a278bfc92 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/reactive.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/reactive.adoc @@ -1,26 +1,30 @@ [[web.reactive]] -== Reactive Web Applications += 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`" +== 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[] +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 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::MyRoutingConfiguration[] -include::code:MyUserHandler[] +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]. +"`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. @@ -34,22 +38,26 @@ You can still enforce your choice by setting the chosen application type to `Spr [[web.reactive.webflux.auto-configuration]] -==== Spring 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 <>). +* 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 {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 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 +=== 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. @@ -74,7 +82,8 @@ When not configured, the following defaults are used: [[web.reactive.webflux.httpcodecs]] -==== HTTP Codecs with HttpMessageReaders and HttpMessageWriters +=== 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. @@ -84,25 +93,26 @@ For example, `+spring.jackson.*+` configuration keys are applied to the Jackson If you need to add or customize codecs, you can create a custom `CodecCustomizer` component, as shown in the following example: -include::code:MyCodecsConfiguration[] +include-code::MyCodecsConfiguration[] -You can also leverage <>. +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 +=== 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] +[configprops,yaml] ---- - spring: - webflux: - static-path-pattern: "/resources/**" +spring: + webflux: + static-path-pattern: "/resources/**" ---- You can also customize the static resource locations by using `spring.web.resources.static-locations`. @@ -119,7 +129,8 @@ TIP: Spring WebFlux applications do not strictly depend on the servlet API, so t [[web.reactive.webflux.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. @@ -143,7 +154,8 @@ The ordering is defined by the order of `HandlerMapping` beans which is by defau [[web.reactive.webflux.template-engines]] -==== 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. @@ -153,29 +165,33 @@ Spring Boot includes auto-configuration support for the following templating eng * https://www.thymeleaf.org[Thymeleaf] * https://mustache.github.io/[Mustache] +NOTE: Not all FreeMarker features are supported with WebFlux. +For more details, check the description of each property. + 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 +=== 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 <>). +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 {spring-framework-docs}/web/webflux/ann-rest-exceptions.html[RFC 9457 Problem Details] support in Spring WebFlux. +Before customizing error handling in Spring Boot directly, you can leverage the {url-spring-framework-docs}/web/webflux/ann-rest-exceptions.html[RFC 9457 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"] +[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" } ---- @@ -188,17 +204,18 @@ 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[] +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 <>. -Applications can ensure that such exceptions are recorded with the observations by {spring-framework-docs}/integration/observability.html#observability.http-server.reactive[setting the handled exception on the observation context]. +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 +==== 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. @@ -206,38 +223,39 @@ Note that the path to the default error view is `error/error`, whereas with Spri For example, to map `404` to a static HTML file, your directory structure would be as follows: -[source,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 Mustache template, your directory structure would be as follows: -[source,indent=0,subs="verbatim"] +[source] ---- - src/ - +- main/ - +- java/ - | + - +- resources/ - +- templates/ - +- error/ - | +- 5xx.mustache - +- +src/ + +- main/ + +- java/ + | + + +- resources/ + +- templates/ + +- error/ + | +- 5xx.mustache + +- ---- [[web.reactive.webflux.web-filters]] -==== 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. @@ -258,15 +276,17 @@ When it does so, the orders shown in the following table will be used: [[web.reactive.reactive-server]] -=== Embedded Reactive Server Support +== 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. +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 +=== 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. @@ -274,45 +294,48 @@ 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 {spring-boot-autoconfigure-module-code}/web/ServerProperties.java[`ServerProperties`] class for a complete list. +TIP: See the javadoc:org.springframework.boot.autoconfigure.web.ServerProperties[] class for a complete list. [[web.reactive.reactive-server.customizing.programmatic]] -===== Programmatic Customization +==== 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[] +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[] +include-code::MyNettyWebServerFactoryCustomizer[] [[web.reactive.reactive-server.customizing.direct]] -===== Customizing ConfigurableReactiveWebServerFactory Directly +==== 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. +See the javadoc:org.springframework.boot.web.reactive.server.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 +== 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: @@ -322,6 +345,6 @@ By default, those resources will be also shared with the Reactor Netty and Jetty 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 <>. +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 77% 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 a4b043333071..cef91069d71e 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. @@ -83,7 +87,8 @@ When not configured, the following defaults are used: [[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). @@ -94,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. @@ -102,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 javadoc:{url-spring-framework-javadoc}/org.springframework.validation.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. @@ -122,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). @@ -141,64 +148,62 @@ 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 javadoc:{url-spring-framework-javadoc}/org.springframework.web.servlet.resource.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 javadoc:org.springframework.boot.autoconfigure.web.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. @@ -220,87 +225,92 @@ The ordering is defined by the order of `HandlerMapping` beans which is by defau |=== + [[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" ---- Spring MVC will throw a `NoHandlerFoundException` if a handler is not found for a request. -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. If no static content is available, `ResourceHttpRequestHandler` will throw a `NoResourceFoundException`. For a `NoHandlerFoundException` to be thrown, 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. @@ -313,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`. @@ -325,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. @@ -339,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 9457 Problem Details] is supported. +As of Spring Framework 6.0, {url-spring-framework-docs}/web/webmvc/mvc-ann-rest-exceptions.html[RFC 9457 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" } ---- @@ -357,77 +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 web observations or the <>. -Applications can ensure that such exceptions are recorded with the observations by {spring-framework-docs}/integration/observability.html#observability.http-server.servlet[setting the handled exception on the observation context]. +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. @@ -439,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 javadoc:{url-spring-framework-javadoc}/org.springframework.web.bind.annotation.CrossOrigin[format=annotation] 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. @@ -459,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 `/*`. @@ -493,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. @@ -524,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 javadoc:org.springframework.boot.web.servlet.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. @@ -543,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. @@ -551,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. @@ -565,15 +581,13 @@ 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. @@ -582,19 +596,20 @@ Common server settings include: * 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 javadoc:org.springframework.boot.autoconfigure.web.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. @@ -604,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`. @@ -619,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 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`. @@ -646,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 javadoc:org.springframework.boot.web.servlet.server.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. @@ -682,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/antora/modules/reference/pages/web/spring-graphql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc new file mode 100644 index 000000000000..cee593cf6229 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-graphql.adoc @@ -0,0 +1,165 @@ +[[web.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: + + +[cols="1,1,1"] +|=== +| Starter | Transport | Implementation + +| `spring-boot-starter-web` +| HTTP +| Spring MVC + +| `spring-boot-starter-websocket` +| WebSocket +| WebSocket for Servlet apps + +| `spring-boot-starter-webflux` +| HTTP, WebSocket +| Spring WebFlux + +| `spring-boot-starter-rsocket` +| TCP, WebSocket +| Spring WebFlux on Reactor Netty +|=== + + + +[[web.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[]. + +NOTE: If you want Spring Boot to detect schema files in all your application modules and dependencies for that location, +you can set configprop:spring.graphql.schema.locations[] to `+"classpath*:graphql/**/"+` (note the `classpath*:` prefix). + +In the following sections, we'll consider this sample GraphQL schema, defining two types and two queries: + +[source,json,subs="verbatim,quotes"] +---- +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. +If you wish to not expose information about the schema, you can disable introspection by setting configprop:spring.graphql.schema.introspection.enabled[] to `false`. + + + +[[web.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 {url-spring-graphql-docs}/request-execution.html#execution.graphqlsource[GraphQlSource builder]. + +Typically, however, applications will not implement `DataFetcher` directly and will instead create {url-spring-graphql-docs}/controllers.html[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[] + + + +[[web.graphql.data-query]] +== Querydsl and QueryByExample Repositories Support + +Spring Data offers support for both Querydsl and QueryByExample repositories. +Spring GraphQL can {url-spring-graphql-docs}/data.html[configure Querydsl and QueryByExample repositories as `DataFetcher`]. + +Spring Data repositories annotated with `@GraphQlRepository` and extending one of: + +* `QuerydslPredicateExecutor` +* `ReactiveQuerydslPredicateExecutor` +* `QueryByExampleExecutor` +* `ReactiveQueryByExampleExecutor` + +are detected by Spring Boot and considered as candidates for `DataFetcher` for matching top-level queries. + + + +[[web.graphql.transports]] +== Transports + + + +[[web.graphql.transports.http-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`. +If you define your own `RouterFunction` beans, you may want to add appropriate `@Order` annotations to ensure that they are sorted correctly. + +The GraphQL WebSocket endpoint is off by default. To enable it: + +* For a Servlet application, add the WebSocket starter `spring-boot-starter-websocket` +* 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 {url-spring-graphql-docs}/transports.html#server.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. + + +{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: + +[configprops,yaml] +---- +spring: + graphql: + cors: + allowed-origins: "https://example.org" + allowed-methods: GET,POST + max-age: 1800s +---- + + + +[[web.graphql.transports.rsocket]] +=== RSocket + +RSocket is also supported as a transport, on top of WebSocket or TCP. +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] + +And then send a request: +include-code::RSocketGraphQlClientExample[tag=request] + + + +[[web.graphql.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 {url-spring-graphql-docs}/controllers.html#controllers.exception-handler[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 + +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 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..5ce9f23a35b1 --- /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 javadoc:org.springframework.boot.autoconfigure.security.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 completely switch off the default web application security configuration, including Actuator security, 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). +To also switch off the `UserDetailsService` configuration, 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 completely switch off the default web application security configuration, including Actuator security, add a bean of type `WebFilterChainProxy` (doing so does not disable the `UserDetailsService` configuration). +To also switch off the `UserDetailsService` configuration, 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/dockerfile b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/partials/dockerfile new file mode 100644 index 000000000000..8985fde04bee --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/partials/dockerfile @@ -0,0 +1,21 @@ +# Perform the extraction in a separate builder container +FROM bellsoft/liberica-openjre-debian:17-cds AS builder +WORKDIR /builder +# This points to the built jar file in the target folder +# Adjust this to 'build/libs/*.jar' if you're using Gradle +ARG JAR_FILE=target/*.jar +# Copy the jar file to the working directory and rename it to application.jar +COPY ${JAR_FILE} application.jar +# Extract the jar file using an efficient layout +RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted + +# This is the runtime container +FROM bellsoft/liberica-openjre-debian:17-cds +WORKDIR /application +# Copy the extracted jar contents from the builder container into the working directory in the runtime container +# Every copy step creates a new docker layer +# This allows docker to only pull the changes it really needs +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/ ./ 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 d7081a4874a7..e6ed71a7032c 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`. NOTE: Custom annotations that are meta-annotated with `@ConfigurationProperties` are not supported. @@ -81,36 +84,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. +Also, the annotation processor cannot auto-detect default values for ``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. @@ -118,22 +123,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 b2ce6c5dae9f..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"] @@ -213,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. @@ -226,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"] @@ -289,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/asciidoc/executable-jar/launching.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/launching.adoc similarity index 84% rename from spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/launching.adoc rename to spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/launching.adoc index 690b85c438d8..30b51f505953 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/launching.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/launching.adoc @@ -1,5 +1,6 @@ [[appendix.executable-jar.launching]] -== Launching Executable Jars += 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. @@ -15,24 +16,25 @@ You can add additional locations by setting an environment variable called `LOAD [[appendix.executable-jar.launching.manifest]] -=== Launcher 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] +[source,manifest] ---- - Main-Class: org.springframework.boot.loader.launch.JarLauncher - Start-Class: com.mycompany.project.MyApplication +Main-Class: org.springframework.boot.loader.launch.JarLauncher +Start-Class: com.mycompany.project.MyApplication ---- For a war file, it would be as follows: -[indent=0] +[source,manifest] ---- - Main-Class: org.springframework.boot.loader.launch.WarLauncher - Start-Class: com.mycompany.project.MyApplication +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. 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..52410e079d3f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/executable-jar/nested-jars.adoc @@ -0,0 +1,154 @@ +[[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" +---- + +NOTE: Spring Boot only uses the classpath index file when the jar or war file is executed with `java -jar`. +It is not used when running the application from the IDE or when using Maven's `spring-boot:run` or Gradle's `bootRun`. + +NOTE: When enabling reproducible builds, the entries in the classpath index file are sorted alphabetically. + + + +[[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 99% 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 ba6ae905b834..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: 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..a9586e407bfb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc @@ -0,0 +1,563 @@ +[[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::["{build-and-artifact-release-type}" == "opensource-milestone"] + + + + spring-milestones + https://repo.spring.io/milestone + + + + + spring-milestones + https://repo.spring.io/milestone + + +endif::[] +ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] + + + + 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::[] + +---- + +ifeval::["{build-type}" == "opensource"] +The preceding listing should give you a working build. +endif::[] + +ifeval::["{build-type}" == "commercial"] +You will also have to configure your build to access the Spring Commercial repository. +This is usual done through a local artifact repository that mirrors the content of the Spring Commercial repository. +Alternatively, while it is not recommended, the Spring Commercial repository can also be accessed directly. +In either case, see https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/spring-enterprise-subscription.html[the Tanzu Spring Runtime documentation] for further details. + +With the addition of the necessary repository configuration, the preceding listing should give you a working build. +endif::[] + +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 c89bab7a653a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ /dev/null @@ -1,1206 +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. -* 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: - -[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) - 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 {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.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 {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 {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 `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 {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 {spring-batch-docs}/monitoring-and-metrics.html[Spring Batch reference documentation]. - - - -[[actuator.metrics.supported.spring-graphql]] -==== Spring GraphQL Metrics - -See the {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`: - -[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}/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 {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: - -[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 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 enable scanning of `@Timed` annotations, you will need to set the configprop:management.observations.annotations.enabled[] property to `true`. -Please 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/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/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 bc9980e824da..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/anchor-rewrite.properties +++ /dev/null @@ -1,1051 +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-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 - -# Spring Boot 3.1 - 3.2 migrations -io.rest-client.resttemplate.http-client=io.rest-client.clienthttprequestfactory - -# 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-39125 -actuator.observability.common-key-values=actuator.observability.common-tags - -# 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 c0e8e22238da..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/application-properties.adoc +++ /dev/null @@ -1,53 +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 mechanisms 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/testcontainers.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 64f87487c86f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/attributes.adoc +++ /dev/null @@ -1,117 +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/reference/{spring-batch-version-antora} -:spring-data: https://spring.io/projects/spring-data -:spring-data-cassandra: https://spring.io/projects/spring-data-cassandra -:spring-data-cassandra-docs: https://docs.spring.io/spring-data/cassandra/reference/{spring-data-cassandra-version-antora} -: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/reference/{spring-data-couchbase-version-antora} -:spring-data-elasticsearch: https://spring.io/projects/spring-data-elasticsearch -:spring-data-elasticsearch-docs: https://docs.spring.io/spring-data/elasticsearch/reference/{spring-data-elasticsearch-version-antora} -:spring-data-envers: https://spring.io/projects/spring-data-envers -:spring-data-geode: https://spring.io/projects/spring-data-geode -:spring-data-jdbc-docs: https://docs.spring.io/spring-data/relational/reference/{spring-data-jdbc-version-antora} -: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/reference/{spring-data-jpa-version-antora} -:spring-data-ldap: https://spring.io/projects/spring-data-ldap -:spring-data-ldap-docs: https://docs.spring.io/spring-data/ldap/reference/{spring-data-ldap-version-antora} -: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/reference/{spring-data-neo4j-version-antora} -: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/relational/reference/{spring-data-r2dbc-version-antora} -: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-docs: https://docs.spring.io/spring-graphql/reference/{spring-graphql-version-antora} -:spring-integration: https://spring.io/projects/spring-integration -:spring-integration-docs: https://docs.spring.io/spring-integration/reference/{spring-integration-version-antora} -:spring-kafka-docs: https://docs.spring.io/spring-kafka/docs/{spring-kafka-version}/reference/ -:spring-pulsar-docs: https://docs.spring.io/spring-pulsar/docs/{spring-pulsar-version}/reference/ -: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/reference/{spring-authorization-server-version-antora} -: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 -:micrometer-tracing-docs: https://docs.micrometer.io/tracing/reference -: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 1956cc4a3188..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 uber 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.launch.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 d06327bb05f3..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 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 a 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 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/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 {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 7651caf4db5a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc +++ /dev/null @@ -1,693 +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-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 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: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - redis: - host: "localhost" - port: 6379 - database: 0 - username: "user" - password: "secret" ----- - -You can also specify the url of the Redis server directly. -When setting the url, the host, port, username and password properties are ignored. -This is shown in the following example: - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- - spring: - data: - redis: - url: "redis://user:secret@localhost:6379" - database: 0 ----- - - -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 `RestClientOptions` 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 `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 <> 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 {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 {spring-data-ldap-docs}/[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 -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/asciidoc/data/sql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/sql.adoc deleted file mode 100644 index 54695c66e611..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/sql.adoc +++ /dev/null @@ -1,525 +0,0 @@ -[[data.sql]] -== SQL Databases -The {spring-framework}[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. -{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.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 {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.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`: - -[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 37ee8b8fade2..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/efficient.adoc +++ /dev/null @@ -1,84 +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.launch.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 <>. - - - -[[deployment.efficient.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 {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/asciidoc/deployment/installing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/installing.adoc deleted file mode 100644 index 7f76bc71018c..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/deployment/installing.adoc +++ /dev/null @@ -1,379 +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 - - Type=exec - ExecStart=/path/to/java/home/bin/java -jar /var/myapp/myapp.jar - WorkingDirectory=/var/myapp - SuccessExitStatus=143 - - [Install] - WantedBy=multi-user.target ----- - -IMPORTANT: Remember to change the `Description`, `User`, `Group`, `ExecStart` and `WorkingDirectory` 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 0ccc798988bd..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/documentation/messaging.adoc +++ /dev/null @@ -1,10 +0,0 @@ -[[documentation.messaging]] -== Messaging -If your application uses any messaging protocol, see one or more of the following sections: - -* *JMS:* <> -* *AMQP:* <> -* *Kafka:* <> -* *Pulsar:* <> -* *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 b1db0c87261a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/jarfile-class.adoc +++ /dev/null @@ -1,34 +0,0 @@ -[[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: - -[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.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/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 2ddda581f49d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/executable-jar/nested-jars.adoc +++ /dev/null @@ -1,148 +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" ----- - -NOTE: Spring Boot only uses the classpath index file when the jar or war file is executed with `java -jar`. -It is not used when running the application from the IDE or when using Maven's `spring-boot:run` or Gradle's `bootRun`. - -NOTE: When enabling reproducible builds, the entries in the classpath index file are sorted alphabetically. - - - -[[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 05c8b85c2187..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 f25f3b4e443a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc +++ /dev/null @@ -1,280 +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. - -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 - -| `ActiveMQConnectionDetails` -| Containers named "symptoma/activemq" - -| `CassandraConnectionDetails` -| Containers named "cassandra" - -| `ElasticsearchConnectionDetails` -| Containers named "elasticsearch" - -| `JdbcConnectionDetails` -| Containers named "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "mssql/server", "mysql", or "postgres" - -| `MongoConnectionDetails` -| Containers named "mongo" - -| `Neo4jConnectionDetails` -| Containers named "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", "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" ----- - - - -[[features.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") - } ----- - 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 51279e97c919..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/logging.adoc +++ /dev/null @@ -1,566 +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. -* Application name: Enclosed in square brackets (logged by default only if configprop:spring.application.name[] is set) -* Thread name: Enclosed in square brackets (may be truncated for console output). -* Correlation ID: If tracing is enabled (not shown in the sample above) -* 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`. - -TIP: If you have a configprop:spring.application.name[] property but don't want it logged you can set configprop:logging.include-application-name[] to `false`. - - -[[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`). -If both properties are set, `logging.file.path` is ignored and only `logging.file.name` is used. - -The following table shows how the `logging.*` properties can be used together: - -.Logging properties -[cols="1,1,4"] -|=== -| configprop:logging.file.name[] | configprop:logging.file.path[] | Description - -| _(none)_ -| _(none)_ -| Console only logging. - -| Specific file (for example, `my.log`) -| _(none)_ -| Writes to the location specified by `logging.file.name`. - The location can be absolute or relative to the current directory. - -| _(none)_ -| Specific directory (for example, `/var/log`) -| Writes `spring.log` to the directory specified by `logging.file.path`. - The directory can be absolute or relative to the current directory. - -| Specific file -| Specific directory -| Writes to the location specified by `logging.file.name` and ignores `logging.file.path`. - The location can be absolute 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 <>, 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.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/systemproperties.html[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/testcontainers.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testcontainers.adoc deleted file mode 100644 index 683d7b656d40..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testcontainers.adoc +++ /dev/null @@ -1,93 +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. - -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.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 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 <> 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 `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/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc deleted file mode 100644 index 6214a0997764..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ /dev/null @@ -1,1135 +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. -* 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. - - - -[[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.observations]] -==== Using Observations -If you annotate <> with `@AutoConfigureObservability`, it auto-configures an `ObservationRegistry`. - - - -[[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`. - -If you annotate <> with `@AutoConfigureObservability`, it auto-configures an in-memory `MeterRegistry`. -Data exporting in sliced tests is not supported with the `@AutoConfigureObservability` annotation. - - - -[[features.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 <> with `@AutoConfigureObservability`, it auto-configures a no-op `Tracer`. -Data exporting in sliced tests is not supported with the `@AutoConfigureObservability` annotation. - - - -[[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.html#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.html[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 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 <>. - -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[] - - - -[[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 - -| `ActiveMQConnectionDetails` -| Containers named "symptoma/activemq" - -| `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` 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" - -| `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 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 f290e312bc17..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/getting-started/first-application.adoc +++ /dev/null @@ -1,545 +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::["{build-and-artifact-release-type}" == "opensource-milestone"] - - - - spring-milestones - https://repo.spring.io/milestone - - - - - spring-milestones - https://repo.spring.io/milestone - - -endif::[] -ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] - - - - 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::[] - ----- - -ifeval::["{build-type}" == "opensource"] -The preceding listing should give you a working build. -endif::[] - -ifeval::["{build-type}" == "commercial"] -You will also have to configure your build to access the Spring Commercial repository. -This is usual done through a local artifact repository that mirrors the content of the Spring Commercial repository. -Alternatively, while it is not recommended, the Spring Commercial repository can also be accessed directly. -In either case, see https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/spring-enterprise-subscription.html[the Tanzu Spring Runtime documentation] for further details. - -With the addition of the necessary repository configuration, the preceding listing should give you a working build. -endif::[] - -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 "`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 <> 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 a489d5f5b8db..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 named `spring` (`_spring` for zsh) or put it in your personal or system-wide bash completion initialization. -On a Debian system, the system-wide scripts are in `/shell-completion/` 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 - encodepassword help init shell 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 2ed78e42e19f..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 at least https://www.java.com[Java 17] and is compatible with versions up to and including Java 23. -{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 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 <> 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/batch.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc deleted file mode 100644 index c03c81520509..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` bean 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 cfae774a9500..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 8568eed283f0..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 its 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 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: - -[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 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: - -[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 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: - -[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 `Repository` implementations 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` implementations 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 over 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 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 {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 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 {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 d889637d1964..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 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/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 5afd42281878..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 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,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 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 <>. - -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 67fc1d167799..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, allowing you to switch to Jedis without specifying a version. - -The following example shows how to accomplish this 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 accomplish this 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 8f327f210511..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 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: - -[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 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 "`<>`" 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 <> 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. -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,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/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc deleted file mode 100644 index 53fc7dd28c5c..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 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/asciidoc/howto/webserver.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc deleted file mode 100644 index 0b9f33e7edbc..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc +++ /dev/null @@ -1,495 +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"] ----- - - 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 {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: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 "`<>`"). -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 4966c197d1b7..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, Apache Pulsar, 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 6ec0da3ca536..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc +++ /dev/null @@ -1,188 +0,0 @@ -[[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 {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 <> 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 <>), 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[] - - - -[[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 <> 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 {spring-framework-api}/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 <> 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/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 12aca393d1a6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging.adoc +++ /dev/null @@ -1,31 +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 and Apache Pulsar. - - -include::messaging/jms.adoc[] - -include::messaging/amqp.adoc[] - -include::messaging/kafka.adoc[] - -include::messaging/pulsar.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 314b0e3a0a93..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}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: - -[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/pulsar.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/pulsar.adoc deleted file mode 100644 index 4e0da8a8779e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/pulsar.adoc +++ /dev/null @@ -1,211 +0,0 @@ -[[messaging.pulsar]] -== Apache Pulsar Support -https://pulsar.apache.org/[Apache Pulsar] is supported by providing auto-configuration of the {spring-pulsar-docs}[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. - -[source,yaml,indent=0,subs="verbatim",configprops,configblocks] ----- -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 {spring-pulsar-docs}reference/pulsar.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 {spring-pulsar-docs}reference/pulsar.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 {spring-pulsar-docs}reference/pulsar.html#pulsar-client[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 <> 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 {spring-pulsar-docs}[reference documentation]. - - - -[[messaging.pulsar.additional-properties]] -=== Additional Pulsar 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 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/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 e1735662d94d..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`, `RestClient` 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 406f22260865..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/structuring-your-code.adoc +++ /dev/null @@ -1,53 +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. - -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 <> 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 0a7f18cb3b3b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/graceful-shutdown.adoc +++ /dev/null @@ -1,34 +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. - -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/spring-graphql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc deleted file mode 100644 index 25308dd3ce1b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc +++ /dev/null @@ -1,155 +0,0 @@ -[[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]. -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: - - -[cols="1,1,1"] -|=== -| Starter | Transport | Implementation - -| `spring-boot-starter-web` -| HTTP -| Spring MVC - -| `spring-boot-starter-websocket` -| WebSocket -| WebSocket for Servlet apps - -| `spring-boot-starter-webflux` -| HTTP, WebSocket -| Spring WebFlux - -| `spring-boot-starter-rsocket` -| TCP, WebSocket -| Spring WebFlux on Reactor Netty -|=== - - - -[[web.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[]. - -NOTE: If you want Spring Boot to detect schema files in all your application modules and dependencies for that location, -you can set configprop:spring.graphql.schema.locations[] to `+"classpath*:graphql/**/"+` (note the `classpath*:` prefix). - -In the following sections, we'll consider this sample GraphQL schema, defining two types and two queries: - -[source,json,indent=0,subs="verbatim,quotes"] ----- -include::{docs-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. -If you wish to not expose information about the schema, you can disable introspection by setting configprop:spring.graphql.schema.introspection.enabled[] to `false`. - - - -[[web.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}/request-execution.html#execution.graphqlsource[GraphQlSource builder]. - -Typically, however, applications will not implement `DataFetcher` directly and will instead create {spring-graphql-docs}/controllers.html[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[] - - - -[[web.graphql.data-query]] -=== Querydsl and QueryByExample Repositories Support -Spring Data offers support for both Querydsl and QueryByExample repositories. -Spring GraphQL can {spring-graphql-docs}/data.html[configure Querydsl and QueryByExample repositories as `DataFetcher`]. - -Spring Data repositories annotated with `@GraphQlRepository` and extending one of: - -* `QuerydslPredicateExecutor` -* `ReactiveQuerydslPredicateExecutor` -* `QueryByExampleExecutor` -* `ReactiveQueryByExampleExecutor` - -are detected by Spring Boot and considered as candidates for `DataFetcher` for matching top-level queries. - - -[[web.graphql.transports]] -=== Transports - - - -[[web.graphql.transports.http-websocket]] -==== HTTP and WebSocket -The GraphQL HTTP endpoint is at HTTP POST `/graphql` by default. -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`. -If you define your own `RouterFunction` beans, you may want to add appropriate `@Order` annotations to ensure that they are sorted correctly. - -The GraphQL WebSocket endpoint is off by default. To enable it: - -* For a Servlet application, add the WebSocket starter `spring-boot-starter-websocket` -* 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}/transports.html#server.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. -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] ----- - spring: - graphql: - cors: - allowed-origins: "https://example.org" - allowed-methods: GET,POST - max-age: 1800s ----- - - - -[[web.graphql.transports.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[]. -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] - -And then send a request: -include::code:RSocketGraphQlClientExample[tag=request] - - - -[[web.graphql.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}/controllers.html#controllers.exception-handler[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 -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. - -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-security.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc deleted file mode 100644 index 15cdcccdd755..000000000000 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc +++ /dev/null @@ -1,399 +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 completely switch off the default web application security configuration, including Actuator security, 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). -To also switch off the `UserDetailsService` configuration, 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 completely switch off the default web application security configuration, including Actuator security, add a bean of type `WebFilterChainProxy` (doing so does not disable the `UserDetailsService` configuration). -To also switch off the `UserDetailsService` configuration, 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: - -[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/loggers/opentelemetry/OpenTelemetryAppenderInitializer.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/loggers/opentelemetry/OpenTelemetryAppenderInitializer.java new file mode 100644 index 000000000000..5a49979f38af --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/loggers/opentelemetry/OpenTelemetryAppenderInitializer.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.docs.actuator.loggers.opentelemetry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +@Component +class OpenTelemetryAppenderInitializer implements InitializingBean { + + private final OpenTelemetry openTelemetry; + + OpenTelemetryAppenderInitializer(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + @Override + public void afterPropertiesSet() { + OpenTelemetryAppender.install(this.openTelemetry); + } + +} 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..9c816309fd2c --- /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,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.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.DynamicPropertyRegistrar; + +@TestConfiguration(proxyBeanMethods = false) +public class MyContainersConfiguration { + + @Bean + public MongoDBContainer mongoDbContainer() { + return new MongoDBContainer("mongo:5.0"); + } + + @Bean + public DynamicPropertyRegistrar mongoDbProperties(MongoDBContainer container) { + return (properties) -> { + 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/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/otherformats/MyCustomFormat.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/logging/structured/otherformats/MyCustomFormat.java new file mode 100644 index 000000000000..6419f20f1102 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/logging/structured/otherformats/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.otherformats; + +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/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/configuretwodatasources/MyAdditionalDataSourceConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyAdditionalDataSourceConfiguration.java new file mode 100644 index 000000000000..7780541c1746 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyAdditionalDataSourceConfiguration.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.dataaccess.configuretwodatasources; + +import org.apache.commons.dbcp2.BasicDataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class MyAdditionalDataSourceConfiguration { + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.datasource") + public BasicDataSource secondDataSource() { + return DataSourceBuilder.create().type(BasicDataSource.class).build(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteAdditionalDataSourceConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteAdditionalDataSourceConfiguration.java new file mode 100644 index 000000000000..829a9c413dd1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteAdditionalDataSourceConfiguration.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.howto.dataaccess.configuretwodatasources; + +import org.apache.commons.dbcp2.BasicDataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class MyCompleteAdditionalDataSourceConfiguration { + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.datasource") + public DataSourceProperties secondDataSourceProperties() { + return new DataSourceProperties(); + } + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.datasource.configuration") + public BasicDataSource secondDataSource( + @Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) { + return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java deleted file mode 100644 index f25837de7274..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java +++ /dev/null @@ -1,59 +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.howto.dataaccess.configuretwodatasources; - -import com.zaxxer.hikari.HikariDataSource; -import org.apache.commons.dbcp2.BasicDataSource; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -@Configuration(proxyBeanMethods = false) -public class MyCompleteDataSourcesConfiguration { - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first") - public DataSourceProperties firstDataSourceProperties() { - return new DataSourceProperties(); - } - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first.configuration") - public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) { - return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); - } - - @Bean - @ConfigurationProperties("app.datasource.second") - public DataSourceProperties secondDataSourceProperties() { - return new DataSourceProperties(); - } - - @Bean - @ConfigurationProperties("app.datasource.second.configuration") - public BasicDataSource secondDataSource( - @Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) { - return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build(); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.java deleted file mode 100644 index c88ba5a7274d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.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.howto.dataaccess.configuretwodatasources; - -import com.zaxxer.hikari.HikariDataSource; -import org.apache.commons.dbcp2.BasicDataSource; - -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -@Configuration(proxyBeanMethods = false) -public class MyDataSourcesConfiguration { - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first") - public DataSourceProperties firstDataSourceProperties() { - return new DataSourceProperties(); - } - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first.configuration") - public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) { - return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); - } - - @Bean - @ConfigurationProperties("app.datasource.second") - public BasicDataSource secondDataSource() { - return DataSourceBuilder.create().type(BasicDataSource.class).build(); - } - -} 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..73b7224142c3 --- /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/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.java new file mode 100644 index 000000000000..8d26116a7350 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.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.docs.howto.dataaccess.usemultipleentitymanagers; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; + +@Configuration(proxyBeanMethods = false) +public class MyAdditionalEntityManagerFactoryConfiguration { + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.jpa") + public JpaProperties secondJpaProperties() { + return new JpaProperties(); + } + + @Qualifier("second") + @Bean(defaultCandidate = false) + public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(@Qualifier("second") DataSource dataSource, + @Qualifier("second") JpaProperties jpaProperties) { + EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties); + return builder.dataSource(dataSource).packages(Order.class).persistenceUnit("second").build(); + } + + private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) { + JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties); + return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null); + } + + private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) { + // ... map JPA properties as needed + return new HibernateJpaVendorAdapter(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyEntityManagerFactoryConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyEntityManagerFactoryConfiguration.java deleted file mode 100644 index 11f898027b27..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyEntityManagerFactoryConfiguration.java +++ /dev/null @@ -1,56 +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.howto.dataaccess.usemultipleentitymanagers; - -import javax.sql.DataSource; - -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.orm.jpa.JpaVendorAdapter; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; - -@Configuration(proxyBeanMethods = false) -public class MyEntityManagerFactoryConfiguration { - - @Bean - @ConfigurationProperties("app.jpa.first") - public JpaProperties firstJpaProperties() { - return new JpaProperties(); - } - - @Bean - public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource, - JpaProperties firstJpaProperties) { - EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(firstJpaProperties); - return builder.dataSource(firstDataSource).packages(Order.class).persistenceUnit("firstDs").build(); - } - - private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) { - JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties); - return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null); - } - - private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) { - // ... map JPA properties as needed - return new HibernateJpaVendorAdapter(); - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/Order.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/Order.java index 333f4f615fd2..a7b4476ca7d8 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/Order.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/Order.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,6 @@ package org.springframework.boot.docs.howto.dataaccess.usemultipleentitymanagers; -class Order { +public class Order { } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/OrderConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/OrderConfiguration.java index 72f00ba0fb48..b6f670d7a99a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/OrderConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/OrderConfiguration.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. @@ -20,7 +20,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @Configuration(proxyBeanMethods = false) -@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "firstEntityManagerFactory") +@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "entityManagerFactory") public class OrderConfiguration { } 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/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/jta/mixingxaandnonxaconnections/nonxa/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/nonxa/MyBean.java index a7d843c9b180..82621580349e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/nonxa/MyBean.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/nonxa/MyBean.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. @@ -22,10 +22,8 @@ public class MyBean { - // tag::code[] public MyBean(@Qualifier("nonXaJmsConnectionFactory") ConnectionFactory connectionFactory) { // ... } - // end::code[] } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/primary/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/primary/MyBean.java index d4d11c7cc3b1..52155ebd5278 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/primary/MyBean.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/primary/MyBean.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. @@ -20,10 +20,8 @@ public class MyBean { - // tag::code[] public MyBean(ConnectionFactory connectionFactory) { // ... } - // end::code[] } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/xa/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/xa/MyBean.java index ab5c4f63a9bf..16a3326821d8 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/xa/MyBean.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/jta/mixingxaandnonxaconnections/xa/MyBean.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. @@ -22,10 +22,8 @@ public class MyBean { - // tag::code[] public MyBean(@Qualifier("xaJmsConnectionFactory") ConnectionFactory connectionFactory) { // ... } - // end::code[] } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/clienthttprequestfactory/configuration/MyClientHttpConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/clienthttprequestfactory/configuration/MyClientHttpConfiguration.java new file mode 100644 index 000000000000..45bb881d6abc --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/clienthttprequestfactory/configuration/MyClientHttpConfiguration.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.io.restclient.clienthttprequestfactory.configuration; + +import java.net.ProxySelector; + +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class MyClientHttpConfiguration { + + @Bean + ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder(ProxySelector proxySelector) { + return ClientHttpRequestFactoryBuilder.jdk() + .withHttpClientCustomizer((builder) -> builder.proxy(proxySelector)); + } + +} 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 index 8fef86df53e3..f8aaef7cb0e7 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,9 @@ import java.time.Duration; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; 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; @@ -31,10 +31,10 @@ 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); + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings + .ofSslBundle(sslBundles.getBundle("mybundle")) + .withReadTimeout(Duration.ofMinutes(2)); + ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder.detect().build(settings); this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build(); } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.java index 7a13e2df9be0..539b2c03f51f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 +29,8 @@ public class MyRestTemplateBuilderConfiguration { @Bean public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) { return configurer.configure(new RestTemplateBuilder()) - .setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(2)); + .connectTimeout(Duration.ofSeconds(5)) + .readTimeout(Duration.ofSeconds(2)); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.java index 5d5407faf9c1..1948d18cad79 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 @@ public class MyService { private final RestTemplate restTemplate; public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) { - this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build(); + this.restTemplate = restTemplateBuilder.sslBundle(sslBundles.getBundle("mybundle")).build(); } public Details someRestCall(String name) { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java index 29c6260acaf3..2e5b0bc93e5b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.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,25 +18,23 @@ import java.time.Duration; -import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.webservices.client.WebServiceMessageSenderFactory; import org.springframework.boot.webservices.client.WebServiceTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ws.client.core.WebServiceTemplate; -import org.springframework.ws.transport.WebServiceMessageSender; @Configuration(proxyBeanMethods = false) public class MyWebServiceTemplateConfiguration { @Bean public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) { - // @formatter:off - WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder() - .setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(2)) - .build(); - return builder.messageSenders(sender).build(); - // @formatter:on + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults() + .withConnectTimeout(Duration.ofSeconds(2)) + .withReadTimeout(Duration.ofSeconds(2)); + builder.httpMessageSenderFactory(WebServiceMessageSenderFactory.http(settings)); + return builder.build(); } } 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/sending/MyBean.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/pulsar/sending/MyBean.java index 7b6610b03e92..ec923a1d584c 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2023 the original author or authors. + * 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. @@ -16,8 +16,6 @@ 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; @@ -30,7 +28,7 @@ public MyBean(PulsarTemplate pulsarTemplate) { this.pulsarTemplate = pulsarTemplate; } - public void someMethod() throws PulsarClientException { + public void someMethod() { this.pulsarTemplate.send("someTopic", "Hello"); } 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/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java index 8d8437fc8f5b..26f57b79663b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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; 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/autoconfiguredrestclient/MyRestTemplateServiceTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java index fdf67b9621b3..9f910024ec2f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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; import org.junit.jupiter.api.Test; @@ -27,7 +27,7 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; -@RestClientTest(RemoteVehicleDetailsService.class) +@RestClientTest(org.springframework.boot.docs.testing.springbootapplications.autoconfiguredrestclient.RemoteVehicleDetailsService.class) class MyRestTemplateServiceTests { @Autowired 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 86% 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..56ef2a42edc7 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; @@ -48,8 +48,7 @@ static class RestTemplateBuilderConfiguration { @Bean RestTemplateBuilder restTemplateBuilder() { - return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1)) - .setReadTimeout(Duration.ofSeconds(1)); + return new RestTemplateBuilder().connectTimeout(Duration.ofSeconds(1)).readTimeout(Duration.ofSeconds(1)); } } 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/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.java index 79cedd792f0c..656626b32e1f 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.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. @@ -27,18 +27,18 @@ class AdditionalHttpMessageConverter extends AbstractHttpMessageConverter { @Override - protected boolean supports(Class clazz) { + protected boolean supports(Class type) { return false; } @Override - protected Object readInternal(Class clazz, HttpInputMessage inputMessage) + protected Object readInternal(Class type, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } @Override - protected void writeInternal(Object t, HttpOutputMessage outputMessage) + protected void writeInternal(Object instance, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/logging/structured/otherformats/MyCustomFormat.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/logging/structured/otherformats/MyCustomFormat.kt new file mode 100644 index 000000000000..fda3abb3571f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/logging/structured/otherformats/MyCustomFormat.kt @@ -0,0 +1,12 @@ +package org.springframework.boot.docs.features.logging.structured.otherformats + +import ch.qos.logback.classic.spi.ILoggingEvent +import org.springframework.boot.logging.structured.StructuredLogFormatter + +class MyCustomFormat : StructuredLogFormatter { + + override fun format(event: ILoggingEvent): String { + return "time=${event.instant} level=${event.level} message=${event.message}\n" + } + +} 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..db3f5b0f9e97 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,18 +18,23 @@ package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime. import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean -import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertyRegistrar; import org.testcontainers.containers.MongoDBContainer @TestConfiguration(proxyBeanMethods = false) class MyContainersConfiguration { @Bean - fun monogDbContainer(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) - return container + fun mongoDbContainer(): MongoDBContainer { + return MongoDBContainer("mongo:5.0") + } + + @Bean + fun mongoDbProperties(container: MongoDBContainer): DynamicPropertyRegistrar { + return DynamicPropertyRegistrar { properties -> + properties.add("spring.data.mongodb.host") { container.host } + properties.add("spring.data.mongodb.port") { container.firstMappedPort } + } } } 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/autoconfiguredspringdatacassandra/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.kt deleted file mode 100644 index 7d422fa1b9b9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.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.autoconfiguredspringdatacassandra - -interface SomeRepository diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.kt deleted file mode 100644 index a9f8f797e7b2..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.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.autoconfiguredspringdatacouchbase - -interface SomeRepository diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.kt deleted file mode 100644 index 5821c6ddd897..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.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.autoconfiguredspringdataelasticsearch - -interface SomeRepository diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.kt deleted file mode 100644 index 51378c51e682..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.kt +++ /dev/null @@ -1,28 +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.withdb - -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest - -@DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -class MyRepositoryTests { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.kt deleted file mode 100644 index ade356e18df4..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.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.autoconfiguredspringdatajpa.withoutdb - -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.orm.jpa.DataJpaTest -import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager - -@DataJpaTest -class MyRepositoryTests(@Autowired val entityManager: TestEntityManager, @Autowired val repository: UserRepository) { - - @Test - fun testExample() { - entityManager.persist(User("sboot", "1234")) - val user = repository.findByUsername("sboot") - assertThat(user?.username).isEqualTo("sboot") - assertThat(user?.employeeNumber).isEqualTo("1234") - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.kt deleted file mode 100644 index 285c37ade28a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.kt +++ /dev/null @@ -1,28 +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.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 val ldapTemplate: LdapTemplate) { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.kt deleted file mode 100644 index 6265c7eb66cb..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.kt +++ /dev/null @@ -1,27 +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.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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.kt deleted file mode 100644 index 7460c673e284..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.kt +++ /dev/null @@ -1,25 +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.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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.kt deleted file mode 100644 index c284cb6d668b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.kt +++ /dev/null @@ -1,27 +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.autoconfiguredspringdataneo4j.propagation - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest - -@DataNeo4jTest -class MyDataNeo4jTests(@Autowired val repository: SomeRepository) { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.kt deleted file mode 100644 index 77b41f7fd07f..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.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.autoconfiguredspringdataneo4j.propagation - -interface SomeRepository diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.kt deleted file mode 100644 index a27027bf6034..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.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.autoconfiguredspringdataredis - -interface SomeRepository diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.kt deleted file mode 100644 index afdcf491fc48..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.kt +++ /dev/null @@ -1,31 +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.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) -class MyRestDocsConfiguration : RestDocsMockMvcConfigurationCustomizer { - - override fun customize(configurer: MockMvcRestDocumentationConfigurer) { - configurer.snippets().withTemplateFormat(TemplateFormats.markdown()) - } - -} \ 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 deleted file mode 100644 index 3bdd188515ec..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt +++ /dev/null @@ -1,40 +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.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.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 - -@WebMvcTest(UserController::class) -@AutoConfigureRestDocs -class MyUserDocumentationTests(@Autowired val mvc: MockMvc) { - - @Test - fun listUsers() { - mvc.perform(MockMvcRequestBuilders.get("/users").accept(MediaType.TEXT_PLAIN)) - .andExpect(MockMvcResultMatchers.status().isOk) - .andDo(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/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.kt deleted file mode 100644 index 7f332a838b5e..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.kt +++ /dev/null @@ -1,31 +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) -class MyRestDocsConfiguration : RestDocsRestAssuredConfigurationCustomizer { - - override fun customize(configurer: RestAssuredRestDocumentationConfigurer) { - configurer.snippets().withTemplateFormat(TemplateFormats.markdown()) - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.kt deleted file mode 100644 index b9b8b0e301d9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.kt +++ /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.autoconfiguredspringrestdocs.withrestassured - -import io.restassured.RestAssured -import io.restassured.specification.RequestSpecification -import org.hamcrest.Matchers -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 org.springframework.restdocs.restassured.RestAssuredRestDocumentation - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@AutoConfigureRestDocs -class MyUserDocumentationTests { - - @Test - fun listUsers(@Autowired documentationSpec: RequestSpecification?, @LocalServerPort port: Int) { - RestAssured.given(documentationSpec) - .filter(RestAssuredRestDocumentation.document("list-users")) - .`when`() - .port(port)["/"] - .then().assertThat() - .statusCode(Matchers.`is`(200)) - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.kt deleted file mode 100644 index be95090d4c05..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.kt +++ /dev/null @@ -1,30 +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.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) -class MyRestDocsConfiguration : RestDocsWebTestClientConfigurationCustomizer { - - override fun customize(configurer: WebTestClientRestDocumentationConfigurer) { - configurer.snippets().withEncoding("UTF-8") - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTests.kt deleted file mode 100644 index 9582e0852684..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTests.kt +++ /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.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 - fun exampleTest() { - // ... - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/VehicleDetails.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/VehicleDetails.kt deleted file mode 100644 index 4ae99479f6c9..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/VehicleDetails.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.jsontests - -data class VehicleDetails(val make: String, val model: String) 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 deleted file mode 100644 index 9879efc62a02..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyControllerTests.kt +++ /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.springmvctests - -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 - -@WebMvcTest(UserVehicleController::class) -class MyControllerTests(@Autowired val mvc: MockMvc) { - - @MockBean - 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")) - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleController.kt deleted file mode 100644 index 45d6c3d2a88d..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleController.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.springmvctests - -class UserVehicleController diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleService.kt deleted file mode 100644 index 3a9c14532180..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/UserVehicleService.kt +++ /dev/null @@ -1,26 +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.springmvctests - -@Suppress("UNUSED_PARAMETER") -class UserVehicleService { - - fun getVehicleDetails(name: String?): VehicleDetails? { - return null - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/VehicleDetails.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/VehicleDetails.kt deleted file mode 100644 index a8032d5f43c5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/VehicleDetails.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.springmvctests - -data class VehicleDetails(val make: String, val model: String) 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 deleted file mode 100644 index ab2d64e11e7a..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt +++ /dev/null @@ -1,43 +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.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.web.reactive.server.WebTestClient -import org.springframework.test.web.reactive.server.expectBody - -@WebFluxTest(UserVehicleController::class) -class MyControllerTests(@Autowired val webClient: WebTestClient) { - - @MockBean - lateinit var userVehicleService: UserVehicleService - - @Test - fun testExample() { - given(userVehicleService.getVehicleDetails("sboot")) - .willReturn(VehicleDetails("Honda", "Civic")) - webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange() - .expectStatus().isOk - .expectBody().isEqualTo("Honda Civic") - } - -} \ 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/springwebfluxtests/UserVehicleController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleController.kt deleted file mode 100644 index ccd70bd0b702..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleController.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.springwebfluxtests - -class UserVehicleController \ 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/springwebfluxtests/UserVehicleService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleService.kt deleted file mode 100644 index b1a7448224d8..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/UserVehicleService.kt +++ /dev/null @@ -1,26 +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 - -@Suppress("UNUSED_PARAMETER") -class UserVehicleService { - - fun getVehicleDetails(name: String?): VehicleDetails? { - return null - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/VehicleDetails.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/VehicleDetails.kt deleted file mode 100644 index 5ca26c27d3a5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springwebfluxtests/VehicleDetails.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.springwebfluxtests - -data class VehicleDetails(val make: String, val model: String) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyApplication.kt deleted file mode 100644 index b7139b67c488..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyApplication.kt +++ /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 -class MyApplication { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.kt deleted file mode 100644 index 75b0ade0b5ea..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.kt +++ /dev/null @@ -1,28 +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.userconfigurationandslicing.scan - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.context.annotation.ComponentScan - -@SpringBootApplication -@ComponentScan("com.example.app", "com.example.another") -class MyApplication { - - // ... - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/custom/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/custom/MyApplication.kt deleted file mode 100644 index d33e20ac7bbe..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/custom/MyApplication.kt +++ /dev/null @@ -1,31 +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.runApplication -import org.springframework.boot.autoconfigure.SpringBootApplication - -@SpringBootApplication -class MyApplication - -fun main(args: Array) { - runApplication(*args) { - setBannerMode(Banner.Mode.OFF) - setAdditionalProfiles("myprofile") - } -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/typical/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/typical/MyApplication.kt deleted file mode 100644 index 38758f47bd82..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/typical/MyApplication.kt +++ /dev/null @@ -1,28 +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.autoconfigure.SpringBootApplication -import org.springframework.boot.docs.using.structuringyourcode.locatingthemainclass.MyApplication -import org.springframework.boot.runApplication - -@SpringBootApplication -class MyApplication - -fun main(args: Array) { - runApplication(*args) -} 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 deleted file mode 100644 index 10e10bae2f5b..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt +++ /dev/null @@ -1,50 +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.withmockenvironment - -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 - -@SpringBootTest -@AutoConfigureMockMvc -class MyMockMvcTests { - - @Test - fun testWithMockMvc(@Autowired mvc: MockMvc) { - mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk) - .andExpect(MockMvcResultMatchers.content().string("Hello World")) - } - - // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient - - @Test - fun testWithWebTestClient(@Autowired webClient: WebTestClient) { - webClient - .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/dynamicproperties/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/dynamicproperties/MyIntegrationTests.kt deleted file mode 100644 index 642ae1cb7659..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/dynamicproperties/MyIntegrationTests.kt +++ /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.docs.features.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 - val neo4j = Neo4jContainer("neo4j:5") - - @DynamicPropertySource - 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/features/testing/testcontainers/serviceconnections/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyIntegrationTests.kt deleted file mode 100644 index 1e09326cbdb5..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyIntegrationTests.kt +++ /dev/null @@ -1,43 +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.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.testcontainers.service.connection.ServiceConnection -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 - @ServiceConnection - val neo4j = Neo4jContainer("neo4j:5") - - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyRedisConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyRedisConfiguration.kt deleted file mode 100644 index d221837eab55..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/serviceconnections/MyRedisConfiguration.kt +++ /dev/null @@ -1,33 +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.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") - } - -} 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 deleted file mode 100644 index e62e5804d7d6..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/vanilla/MyIntegrationTests.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.testcontainers.vanilla - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest -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 - val neo4j = Neo4jContainer("neo4j:5") - - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MyTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MyTests.kt deleted file mode 100644 index d6bd28b10138..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/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.utilities.testresttemplate - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.springframework.boot.test.web.client.TestRestTemplate - -class MyTests { - - private val template = TestRestTemplate() - - @Test - fun testRequest() { - val headers = template.getForEntity("https://myhost.example.com/example", String::class.java) - assertThat(headers.headers.location).hasHost("other.example.com") - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyAdditionalDataSourceConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyAdditionalDataSourceConfiguration.kt new file mode 100644 index 000000000000..401f08d7f417 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyAdditionalDataSourceConfiguration.kt @@ -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.dataaccess.configuretwodatasources + +import org.apache.commons.dbcp2.BasicDataSource + +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.jdbc.DataSourceBuilder +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration(proxyBeanMethods = false) +class MyAdditionalDataSourceConfiguration { + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.datasource") + fun secondDataSource(): BasicDataSource { + return DataSourceBuilder.create().type(BasicDataSource::class.java).build() + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteAdditionalDataSourceConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteAdditionalDataSourceConfiguration.kt new file mode 100644 index 000000000000..cccbbff269d2 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteAdditionalDataSourceConfiguration.kt @@ -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.howto.dataaccess.configuretwodatasources + +import org.apache.commons.dbcp2.BasicDataSource + +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration(proxyBeanMethods = false) +class MyCompleteAdditionalDataSourceConfiguration { + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.datasource") + fun secondDataSourceProperties(): DataSourceProperties { + return DataSourceProperties() + } + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.datasource.configuration") + fun secondDataSource(secondDataSourceProperties: DataSourceProperties): BasicDataSource { + return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource::class.java).build() + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt deleted file mode 100644 index 24d142717a08..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt +++ /dev/null @@ -1,56 +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.howto.dataaccess.configuretwodatasources - -import com.zaxxer.hikari.HikariDataSource -import org.apache.commons.dbcp2.BasicDataSource -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary - -@Configuration(proxyBeanMethods = false) -class MyCompleteDataSourcesConfiguration { - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first") - fun firstDataSourceProperties(): DataSourceProperties { - return DataSourceProperties() - } - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first.configuration") - fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource { - return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build() - } - - @Bean - @ConfigurationProperties("app.datasource.second") - fun secondDataSourceProperties(): DataSourceProperties { - return DataSourceProperties() - } - - @Bean - @ConfigurationProperties("app.datasource.second.configuration") - fun secondDataSource(secondDataSourceProperties: DataSourceProperties): BasicDataSource { - return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource::class.java).build() - } - -} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt deleted file mode 100644 index 2e47fb8b10cf..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt +++ /dev/null @@ -1,51 +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.howto.dataaccess.configuretwodatasources - -import com.zaxxer.hikari.HikariDataSource -import org.apache.commons.dbcp2.BasicDataSource -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.jdbc.DataSourceBuilder -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary - -@Configuration(proxyBeanMethods = false) -class MyDataSourcesConfiguration { - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first") - fun firstDataSourceProperties(): DataSourceProperties { - return DataSourceProperties() - } - - @Bean - @Primary - @ConfigurationProperties("app.datasource.first.configuration") - fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource { - return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build() - } - - @Bean - @ConfigurationProperties("app.datasource.second") - fun secondDataSource(): BasicDataSource { - return DataSourceBuilder.create().type(BasicDataSource::class.java).build() - } - -} 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..bd215950c239 --- /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/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt new file mode 100644 index 000000000000..55eefdd7e58e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyAdditionalEntityManagerFactoryConfiguration.kt @@ -0,0 +1,62 @@ +/* + * 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.dataaccess.usemultipleentitymanagers + +import javax.sql.DataSource + +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.orm.jpa.JpaVendorAdapter +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter + +@Suppress("UNUSED_PARAMETER") +@Configuration(proxyBeanMethods = false) +class MyAdditionalEntityManagerFactoryConfiguration { + + @Qualifier("second") + @Bean(defaultCandidate = false) + @ConfigurationProperties("app.jpa") + fun secondJpaProperties(): JpaProperties { + return JpaProperties() + } + + @Qualifier("second") + @Bean(defaultCandidate = false) + fun firstEntityManagerFactory( + @Qualifier("second") dataSource: DataSource, + @Qualifier("second") jpaProperties: JpaProperties + ): LocalContainerEntityManagerFactoryBean { + val builder = createEntityManagerFactoryBuilder(jpaProperties) + return builder.dataSource(dataSource).packages(Order::class.java).persistenceUnit("second").build() + } + + private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder { + val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties) + return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.properties, null) + } + + private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter { + // ... map JPA properties as needed + return HibernateJpaVendorAdapter() + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyEntityManagerFactoryConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyEntityManagerFactoryConfiguration.kt deleted file mode 100644 index ef844b3ab3e4..000000000000 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/usemultipleentitymanagers/MyEntityManagerFactoryConfiguration.kt +++ /dev/null @@ -1,59 +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.howto.dataaccess.usemultipleentitymanagers - -import javax.sql.DataSource - -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.orm.jpa.JpaVendorAdapter -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter - -@Suppress("UNUSED_PARAMETER") -@Configuration(proxyBeanMethods = false) -class MyEntityManagerFactoryConfiguration { - - @Bean - @ConfigurationProperties("app.jpa.first") - fun firstJpaProperties(): JpaProperties { - return JpaProperties() - } - - @Bean - fun firstEntityManagerFactory( - firstDataSource: DataSource?, - firstJpaProperties: JpaProperties - ): LocalContainerEntityManagerFactoryBean { - val builder = createEntityManagerFactoryBuilder(firstJpaProperties) - return builder.dataSource(firstDataSource).packages(Order::class.java).persistenceUnit("firstDs").build() - } - - private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder { - val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties) - return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.properties, null) - } - - private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter { - // ... map JPA properties as needed - return HibernateJpaVendorAdapter() - } - -} 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/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt similarity index 91% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt index 0399da6b794e..b411fb883d60 100644 --- 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/howto/deployment/cloud/cloudfoundry/bindingtoservices/MyBean.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.deployment.cloud.cloudfoundry.bindingtoservices +package org.springframework.boot.docs.howto.deployment.cloud.cloudfoundry.bindingtoservices import org.springframework.context.EnvironmentAware import org.springframework.core.env.Environment 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/clienthttprequestfactory/configuration/MyClientHttpConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/clienthttprequestfactory/configuration/MyClientHttpConfiguration.kt new file mode 100644 index 000000000000..d3a7827b109c --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/clienthttprequestfactory/configuration/MyClientHttpConfiguration.kt @@ -0,0 +1,18 @@ +package org.springframework.boot.docs.io.restclient.clienthttprequestfactory.configuration + +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.net.ProxySelector +import java.net.http.HttpClient + +@Configuration(proxyBeanMethods = false) +class MyClientHttpConfiguration { + + @Bean + fun clientHttpRequestFactoryBuilder(proxySelector: ProxySelector): ClientHttpRequestFactoryBuilder<*>? { + return ClientHttpRequestFactoryBuilder.jdk() + .withHttpClientCustomizer { builder -> builder.proxy(proxySelector) } + } + +} \ No newline at end of file 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 index e153262f8248..c4133fa8fbbc 100644 --- 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 @@ -16,9 +16,9 @@ package org.springframework.boot.docs.io.restclient.restclient.ssl.settings +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; 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 @@ -29,10 +29,10 @@ class MyService(restClientBuilder: RestClient.Builder, sslBundles: SslBundles) { private val restClient: RestClient init { - val settings = ClientHttpRequestFactorySettings.DEFAULTS + val settings = ClientHttpRequestFactorySettings.defaults() .withReadTimeout(Duration.ofMinutes(2)) .withSslBundle(sslBundles.getBundle("mybundle")) - val requestFactory = ClientHttpRequestFactories.get(settings) + val requestFactory = ClientHttpRequestFactoryBuilder.detect().build(settings); restClient = restClientBuilder .baseUrl("https://example.org") .requestFactory(requestFactory).build() diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.kt index cbf8edfe543e..d4312d5731e0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/customization/MyRestTemplateBuilderConfiguration.kt @@ -27,8 +27,8 @@ class MyRestTemplateBuilderConfiguration { @Bean fun restTemplateBuilder(configurer: RestTemplateBuilderConfigurer): RestTemplateBuilder { - return configurer.configure(RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(2)) + return configurer.configure(RestTemplateBuilder()).connectTimeout(Duration.ofSeconds(5)) + .readTimeout(Duration.ofSeconds(2)) } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.kt index 5787b7f4d89c..4e5f6e623bb2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/restclient/resttemplate/ssl/MyService.kt @@ -27,7 +27,7 @@ class MyService(restTemplateBuilder: RestTemplateBuilder, sslBundles: SslBundles private val restTemplate: RestTemplate init { - restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build() + restTemplate = restTemplateBuilder.sslBundle(sslBundles.getBundle("mybundle")).build() } fun someRestCall(name: String): Details { diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt index c9a86da99df7..524e0d6e7983 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt @@ -16,7 +16,8 @@ package org.springframework.boot.docs.io.webservices.template -import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings +import org.springframework.boot.webservices.client.WebServiceMessageSenderFactory import org.springframework.boot.webservices.client.WebServiceTemplateBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -28,11 +29,11 @@ class MyWebServiceTemplateConfiguration { @Bean fun webServiceTemplate(builder: WebServiceTemplateBuilder): WebServiceTemplate { - val sender = HttpWebServiceMessageSenderBuilder() - .setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(2)) - .build() - return builder.messageSenders(sender).build() + val settings = ClientHttpRequestFactorySettings.defaults() + .withConnectTimeout(Duration.ofSeconds(2)) + .withReadTimeout(Duration.ofSeconds(2)) + builder.httpMessageSenderFactory(WebServiceMessageSenderFactory.http(settings)) + return builder.build() } } 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/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/features/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.kt index 18b31d60388c..76befa9d829c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/additionalautoconfigurationandslicing/MyJdbcTests.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.kt similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.kt index 9ccab1796f9b..47e511c093d1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjdbc/MyTransactionalTests.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.kt similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.kt index 62a39624c12d..254ac05400c1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired 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/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt index 9d6d561da02a..02cb3c106dcd 100644 --- 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/testing/springbootapplications/autoconfiguredrestclient/MyRestClientServiceTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test 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/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt index 5b51eae5c68d..202d645024b1 100644 --- 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/testing/springbootapplications/autoconfiguredrestclient/MyRestTemplateServiceTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.kt similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.kt index f03184e7060d..e64d6cc41ed0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredrestclient/RemoteVehicleDetailsService.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.kt similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.kt index 0d12dd405d0d..05674bc1892e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/MyDataCassandraTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.kt new file mode 100644 index 000000000000..d32bf41a1c53 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacassandra/SomeRepository.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.kt index d2268b776c67..553f920066c7 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/MyDataCouchbaseTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.kt new file mode 100644 index 000000000000..ef5d7f5bd7e1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatacouchbase/SomeRepository.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.kt index efe4e7dd47cf..c123d7d1170b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/MyDataElasticsearchTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.kt new file mode 100644 index 000000000000..6f5dfd2120a9 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataelasticsearch/SomeRepository.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + +interface SomeRepository diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.kt similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.kt index 40de9a76e7ea..4f898a4e837b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/MyNonTransactionalTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.kt new file mode 100644 index 000000000000..678805e5dd54 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withdb/MyRepositoryTests.kt @@ -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.autoconfiguredspringdatajpa.withdb + +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class MyRepositoryTests { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.kt new file mode 100644 index 000000000000..0b46d184f0da --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/MyRepositoryTests.kt @@ -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.springbootapplications.autoconfiguredspringdatajpa.withoutdb + +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.orm.jpa.DataJpaTest +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager + +@DataJpaTest +class MyRepositoryTests(@Autowired val entityManager: TestEntityManager, @Autowired val repository: UserRepository) { + + @Test + fun testExample() { + entityManager.persist(User("sboot", "1234")) + val user = repository.findByUsername("sboot") + assertThat(user?.username).isEqualTo("sboot") + assertThat(user?.employeeNumber).isEqualTo("1234") + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.kt similarity index 78% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.kt index a747adfa93cb..d317da025f11 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/User.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. @@ -14,6 +14,6 @@ * 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(val username: String, val employeeNumber: String) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.kt similarity index 79% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.kt index ae06280c40a8..f517637624f2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatajpa/withoutdb/UserRepository.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.kt new file mode 100644 index 000000000000..c52fc28a701f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/inmemory/MyDataLdapTests.kt @@ -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.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 val ldapTemplate: LdapTemplate) { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.kt new file mode 100644 index 000000000000..5e905e60436f --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataldap/server/MyDataLdapTests.kt @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.kt index 80d27503fa3a..72f9b345ca6d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdatamongodb/MyDataMongoDbTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.kt new file mode 100644 index 000000000000..228e0173efb5 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/nopropagation/MyDataNeo4jTests.kt @@ -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.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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.kt new file mode 100644 index 000000000000..a69c02384546 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/MyDataNeo4jTests.kt @@ -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.propagation + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest + +@DataNeo4jTest +class MyDataNeo4jTests(@Autowired val repository: SomeRepository) { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.kt new file mode 100644 index 000000000000..e13ff03032b6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataneo4j/propagation/SomeRepository.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.kt index 6b5e3dbb1500..5a619337f323 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/MyDataRedisTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.kt new file mode 100644 index 000000000000..e328b041da5e --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringdataredis/SomeRepository.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.kt new file mode 100644 index 000000000000..4c1336a959bb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyRestDocsConfiguration.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.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) +class MyRestDocsConfiguration : RestDocsMockMvcConfigurationCustomizer { + + override fun customize(configurer: MockMvcRestDocumentationConfigurer) { + configurer.snippets().withTemplateFormat(TemplateFormats.markdown()) + } + +} \ 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/MyResultHandlerConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.kt similarity index 85% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.kt index 44a53da8d919..fe9e6b8f432a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyResultHandlerConfiguration.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt new file mode 100644 index 000000000000..3f4b2445c434 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.kt @@ -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.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.assertj.MockMvcTester + +@WebMvcTest(UserController::class) +@AutoConfigureRestDocs +class MyUserDocumentationTests(@Autowired val mvc: MockMvcTester) { + + @Test + fun listUsers() { + 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/autoconfiguredspringrestdocs/withmockmvc/UserController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.kt similarity index 77% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.kt index 527b12d88ddc..d932051c0cd0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.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. @@ -14,6 +14,6 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc class UserController diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.kt new file mode 100644 index 000000000000..b5338b1b4ddb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyRestDocsConfiguration.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.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) +class MyRestDocsConfiguration : RestDocsRestAssuredConfigurationCustomizer { + + override fun customize(configurer: RestAssuredRestDocumentationConfigurer) { + configurer.snippets().withTemplateFormat(TemplateFormats.markdown()) + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.kt new file mode 100644 index 000000000000..9aaa3ea60377 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withrestassured/MyUserDocumentationTests.kt @@ -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.autoconfiguredspringrestdocs.withrestassured + +import io.restassured.RestAssured +import io.restassured.specification.RequestSpecification +import org.hamcrest.Matchers +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 org.springframework.restdocs.restassured.RestAssuredRestDocumentation + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureRestDocs +class MyUserDocumentationTests { + + @Test + fun listUsers(@Autowired documentationSpec: RequestSpecification?, @LocalServerPort port: Int) { + RestAssured.given(documentationSpec) + .filter(RestAssuredRestDocumentation.document("list-users")) + .`when`() + .port(port)["/"] + .then().assertThat() + .statusCode(Matchers.`is`(200)) + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.kt new file mode 100644 index 000000000000..845ecf8eb489 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyRestDocsConfiguration.kt @@ -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.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) +class MyRestDocsConfiguration : RestDocsWebTestClientConfigurationCustomizer { + + override fun customize(configurer: WebTestClientRestDocumentationConfigurer) { + configurer.snippets().withEncoding("UTF-8") + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.kt similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.kt index fa75f024d279..8161976e285c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyUsersDocumentationTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.kt similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.kt index 157dc98d3091..2ba6f1b7d0a5 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withwebtestclient/MyWebTestClientBuilderCustomizerConfiguration.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.kt index a72ffe0ac3d6..b960a69c7657 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/MyWebServiceClientTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Request.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Request.kt similarity index 80% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Request.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Request.kt index eb6ff9e58381..059a9473e1e7 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Request.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Request.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Response.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Response.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Response.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Response.kt index df939da9a7ab..e99ec4f8f88c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/Response.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/Response.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.kt similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.kt index b6bedacb577b..aafdd7fea4ca 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/client/SomeWebService.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.kt similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.kt index 9f8ee2c07abf..7f7ddd8b4cd2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/ExampleEndpoint.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.kt similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.kt index ce4ef870dd3e..f7b6bf746ce2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredwebservices/server/MyWebServiceServerTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.kt similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.kt index 4fe954cb6a2c..db06422fafd7 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/detectingwebapptype/MyWebFluxTests.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTests.kt new file mode 100644 index 000000000000..4c6e8d9fbf29 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTests.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.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 + fun exampleTest() { + // ... + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.kt similarity index 79% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.kt index f2598ca0c9fa..211fd9b7841b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/excludingconfiguration/MyTestsConfiguration.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. @@ -14,6 +14,6 @@ * 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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTests.kt similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTests.kt index 7616cdf2bf18..97988a741f0e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jmx/MyJmxTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jmx/MyJmxTests.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jmx/SampleApp.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jmx/SampleApp.kt similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jmx/SampleApp.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jmx/SampleApp.kt index c9428ec14d15..b5651cc454f1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jmx/SampleApp.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jmx/SampleApp.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonAssertJTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonAssertJTests.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonAssertJTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonAssertJTests.kt index beea17d5de52..4ac615c2ec9a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonAssertJTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonAssertJTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.within diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonTests.kt similarity index 91% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonTests.kt index 6f13c3094407..46712e5ea3fc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/MyJsonTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/MyJsonTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/SomeObject.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/SomeObject.kt similarity index 81% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/SomeObject.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/SomeObject.kt index 15a544a1d81f..b279ce708848 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/jsontests/SomeObject.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/SomeObject.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. @@ -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 @Suppress("UNUSED_PARAMETER") class SomeObject(value: Float) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/VehicleDetails.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/VehicleDetails.kt new file mode 100644 index 000000000000..0d019b931e10 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/jsontests/VehicleDetails.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + +data class VehicleDetails(val make: String, val model: String) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt similarity index 91% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt index f8b0f3ea0940..c1d4592c4966 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GraphQlIntegrationTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt similarity index 90% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt index 902d9cec26a5..53980c411620 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springgraphqltests/GreetingControllerTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.kt new file mode 100644 index 000000000000..c1f2027d8abb --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.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.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.http.MediaType +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: MockMvcTester) { + + @MockitoBean + lateinit var userVehicleService: UserVehicleService + + @Test + fun testExample() { + given(userVehicleService.getVehicleDetails("sboot")) + .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/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt similarity index 79% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyHtmlUnitTests.kt index 27119f4052f8..bea308c29740 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/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. @@ -14,21 +14,21 @@ * 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.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/testing/springbootapplications/springmvctests/UserVehicleController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleController.kt new file mode 100644 index 000000000000..32b4b58874d1 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleController.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleService.kt new file mode 100644 index 000000000000..a0aa92b8fffa --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/UserVehicleService.kt @@ -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.testing.springbootapplications.springmvctests + +@Suppress("UNUSED_PARAMETER") +class UserVehicleService { + + fun getVehicleDetails(name: String?): VehicleDetails? { + return null + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/VehicleDetails.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/VehicleDetails.kt new file mode 100644 index 000000000000..448560939f93 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springmvctests/VehicleDetails.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + +data class VehicleDetails(val make: String, val model: String) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt new file mode 100644 index 000000000000..2a30fe32528a --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/MyControllerTests.kt @@ -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.springwebfluxtests + +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.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) { + + @MockitoBean + lateinit var userVehicleService: UserVehicleService + + @Test + fun testExample() { + given(userVehicleService.getVehicleDetails("sboot")) + .willReturn(VehicleDetails("Honda", "Civic")) + webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange() + .expectStatus().isOk + .expectBody().isEqualTo("Honda Civic") + } + +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleController.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleController.kt new file mode 100644 index 000000000000..7d3ad1f583df --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleController.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleService.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleService.kt new file mode 100644 index 000000000000..8696f9f64e20 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/UserVehicleService.kt @@ -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.testing.springbootapplications.springwebfluxtests + +@Suppress("UNUSED_PARAMETER") +class UserVehicleService { + + fun getVehicleDetails(name: String?): VehicleDetails? { + return null + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/VehicleDetails.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/VehicleDetails.kt new file mode 100644 index 000000000000..ea3be41f1946 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/springwebfluxtests/VehicleDetails.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 + +data class VehicleDetails(val make: String, val model: String) diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyApplication.kt new file mode 100644 index 000000000000..479133df0013 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyApplication.kt @@ -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 +class MyApplication { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.kt similarity index 83% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.kt index f0c056d8eff9..b138ad38d041 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyMongoConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.kt similarity index 85% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.kt index 38372dbc8088..cf5129aafd25 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebConfiguration.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.kt similarity index 82% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.kt index 7168fa99035a..eaa57fba45dd 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/MyWebMvcConfigurer.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. @@ -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/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.kt new file mode 100644 index 000000000000..f01db92229f0 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/userconfigurationandslicing/scan/MyApplication.kt @@ -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") +class MyApplication { + + // ... + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.kt similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.kt index dcc79a971e29..2ae91cee8bab 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingapplicationarguments/MyApplicationArgumentTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/always/MyApplicationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/always/MyApplicationTests.kt similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/always/MyApplicationTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/always/MyApplicationTests.kt index b4f07a60524b..b065a6c664f9 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/usingmain/always/MyApplicationTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/always/MyApplicationTests.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.features.testing.springbootapplications.usingmain.custom.always +package org.springframework.boot.docs.testing.springbootapplications.usingmain.always import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/custom/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/custom/MyApplication.kt new file mode 100644 index 000000000000..08408803b4f6 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/custom/MyApplication.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.springbootapplications.usingmain.custom + +import org.springframework.boot.Banner +import org.springframework.boot.runApplication +import org.springframework.boot.autoconfigure.SpringBootApplication + +@SpringBootApplication +class MyApplication + +fun main(args: Array) { + runApplication(*args) { + setBannerMode(Banner.Mode.OFF) + setAdditionalProfiles("myprofile") + } +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/typical/MyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/typical/MyApplication.kt new file mode 100644 index 000000000000..a498939c4c27 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/usingmain/typical/MyApplication.kt @@ -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.usingmain.typical + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.docs.using.structuringyourcode.locatingthemainclass.MyApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class MyApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt new file mode 100644 index 000000000000..476ff42d4d37 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.kt @@ -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.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.assertj.MockMvcTester + +@SpringBootTest +@AutoConfigureMockMvc +class MyMockMvcTests { + + @Test + 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 + + @Test + fun testWithWebTestClient(@Autowired webClient: WebTestClient) { + webClient + .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/springbootapplications/withmockenvironment/MyMockWebTestClientTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.kt similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.kt index 08adafec5870..6fa96f8b06bc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockWebTestClientTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.kt similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.kt index 1a769cfd6370..5a330401134d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortTestRestTemplateTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.kt similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.kt index 9d3aa6930375..b196d71e7d5c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/springbootapplications/withrunningserver/MyRandomPortWebTestClientTests.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. @@ -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 import org.springframework.beans.factory.annotation.Autowired 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 index c2aa497a5702..ca5697de93c8 100644 --- 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 @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.springframework.boot.docs.testing.testcontainers.dynamicproperties; +package org.springframework.boot.docs.testing.testcontainers.dynamicproperties import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest 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 index a5e1071d544e..f50a262ad32f 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.testing.testcontainers.serviceconnections; +package org.springframework.boot.docs.testing.testcontainers.serviceconnections import org.junit.jupiter.api.Test; import org.testcontainers.containers.Neo4jContainer; 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 index fb5ac0340673..35f7027cfdfc 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.testing.testcontainers.serviceconnections; +package org.springframework.boot.docs.testing.testcontainers.serviceconnections import org.springframework.boot.test.context.TestConfiguration import org.springframework.boot.testcontainers.service.connection.ServiceConnection 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 index 587ed84cce62..f2215052995f 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.testing.testcontainers.vanilla; +package org.springframework.boot.docs.testing.testcontainers.vanilla import org.junit.jupiter.api.Test; import org.testcontainers.containers.Neo4jContainer; diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/Config.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/Config.kt similarity index 78% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/Config.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/Config.kt index ed2572df2afd..ae69f62bbd25 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/Config.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/Config.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. @@ -14,6 +14,6 @@ * 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/kotlin/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.kt similarity index 84% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.kt index b61258c6b76a..ef24a4a79ff4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/configdataapplicationcontextinitializer/MyConfigFileTests.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. @@ -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/kotlin/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTests.kt similarity index 88% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTests.kt index dfbc01759529..623f693867de 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/outputcapture/MyOutputCaptureTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/outputcapture/MyOutputCaptureTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testpropertyvalues/MyEnvironmentTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testpropertyvalues/MyEnvironmentTests.kt similarity index 87% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testpropertyvalues/MyEnvironmentTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testpropertyvalues/MyEnvironmentTests.kt index eddf9ab0c4e1..4930df4f3e20 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testpropertyvalues/MyEnvironmentTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testpropertyvalues/MyEnvironmentTests.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. @@ -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.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTests.kt similarity index 86% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTests.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTests.kt index 35ca97396573..9f592c34cdd8 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTests.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. @@ -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 org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -41,8 +41,8 @@ class MySpringBootTests(@Autowired val template: TestRestTemplate) { @Bean fun restTemplateBuilder(): RestTemplateBuilder { - return RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1)) - .setReadTimeout(Duration.ofSeconds(1)) + return RestTemplateBuilder().connectTimeout(Duration.ofSeconds(1)) + .readTimeout(Duration.ofSeconds(1)) } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.kt similarity index 92% rename from spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.kt rename to spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.kt index 5bb52cd03a6a..6d3c10a267ba 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MySpringBootTestsConfiguration.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. @@ -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 org.springframework.boot.SpringBootConfiguration import org.springframework.boot.autoconfigure.ImportAutoConfiguration diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MyTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MyTests.kt new file mode 100644 index 000000000000..dac2139855df --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/testing/utilities/testresttemplate/MyTests.kt @@ -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.utilities.testresttemplate + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.boot.test.web.client.TestRestTemplate + +class MyTests { + + private val template = TestRestTemplate() + + @Test + fun testRequest() { + val headers = template.getForEntity("https://myhost.example.com/example", String::class.java) + assertThat(headers.headers.location).hasHost("other.example.com") + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.kt index d851f33d004e..455d1f3c14db 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/servlet/springmvc/messageconverters/AdditionalHttpMessageConverter.kt @@ -25,17 +25,17 @@ import java.io.IOException open class AdditionalHttpMessageConverter : AbstractHttpMessageConverter() { - override fun supports(clazz: Class<*>): Boolean { + override fun supports(type: Class<*>): Boolean { return false } @Throws(IOException::class, HttpMessageNotReadableException::class) - override fun readInternal(clazz: Class<*>, inputMessage: HttpInputMessage): Any { + override fun readInternal(type: Class<*>, inputMessage: HttpInputMessage): Any { return Any() } @Throws(IOException::class, HttpMessageNotWritableException::class) - override fun writeInternal(t: Any, outputMessage: HttpOutputMessage) { + override fun writeInternal(instance: Any, outputMessage: HttpOutputMessage) { } } diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfigurationTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfigurationTests.java index aa3be89a0cd4..143ab498551b 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfigurationTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfigurationTests.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,37 +21,41 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link MyCompleteDataSourcesConfiguration}. + * Tests for {@link MyCompleteAdditionalDataSourceConfiguration}. * * @author Stephane Nicoll */ -@ExtendWith(SpringExtension.class) @SpringBootTest -@Import(MyCompleteDataSourcesConfiguration.class) +@Import(MyCompleteAdditionalDataSourceConfiguration.class) class MyCompleteDataSourcesConfigurationTests { @Autowired private ApplicationContext context; + @Autowired + private DataSource dataSource; + + @Autowired + @Qualifier("second") + private DataSource secondDataSource; + @Test void validateConfiguration() throws SQLException { assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(2); - DataSource dataSource = this.context.getBean(DataSource.class); - assertThat(this.context.getBean("firstDataSource")).isSameAs(dataSource); - assertThat(dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); - DataSource secondDataSource = this.context.getBean("secondDataSource", DataSource.class); - assertThat(secondDataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); + assertThat(this.context.getBean("dataSource")).isSameAs(this.dataSource); + assertThat(this.dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); + assertThat(this.context.getBean("secondDataSource")).isSameAs(this.secondDataSource); + assertThat(this.secondDataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); } } diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java index 6034bae7659d..dbee0f9c37a4 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.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. @@ -22,39 +22,44 @@ import org.apache.commons.dbcp2.BasicDataSource; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link MyDataSourcesConfiguration}. + * Tests for {@link MyAdditionalDataSourceConfiguration}. * * @author Stephane Nicoll */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(properties = { "app.datasource.second.url=jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1", - "app.datasource.second.max-total=42" }) -@Import(MyDataSourcesConfiguration.class) +@SpringBootTest(properties = { "app.datasource.url=jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1", "app.datasource.max-total=42" }) +@Import(MyAdditionalDataSourceConfiguration.class) class MyDataSourcesConfigurationTests { @Autowired private ApplicationContext context; + @Autowired + private DataSource dataSource; + + @Autowired + @Qualifier("second") + private DataSource secondDataSource; + @Test void validateConfiguration() throws SQLException { assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(2); - DataSource dataSource = this.context.getBean(DataSource.class); - assertThat(this.context.getBean("firstDataSource")).isSameAs(dataSource); - assertThat(dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); - BasicDataSource secondDataSource = this.context.getBean("secondDataSource", BasicDataSource.class); - assertThat(secondDataSource.getUrl()).isEqualTo("jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1"); - assertThat(secondDataSource.getMaxTotal()).isEqualTo(42); + assertThat(this.context.getBean("dataSource")).isSameAs(this.dataSource); + assertThat(this.dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); + assertThat(this.context.getBean("secondDataSource")).isSameAs(this.secondDataSource); + assertThat(this.secondDataSource).extracting((dataSource) -> ((BasicDataSource) dataSource).getUrl()) + .isEqualTo("jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1"); + assertThat(this.secondDataSource).extracting((dataSource) -> ((BasicDataSource) dataSource).getMaxTotal()) + .isEqualTo(42); } } 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 bb7b21099eee..923fb02ca1c9 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -33,6 +33,21 @@ bom { ] } } + library("ClickHouse", "0.6.5") { + group("com.clickhouse") { + modules = [ + "clickhouse-jdbc", + "clickhouse-r2dbc" + ] + } + } + library("Commons Compress", "1.25.0") { + group("org.apache.commons") { + modules = [ + "commons-compress" + ] + } + } library("Commons FileUpload", "1.5") { group("commons-fileupload") { modules = [ @@ -40,10 +55,10 @@ bom { ] } } - library("Jakarta Inject", "2.0.1") { - group("jakarta.inject") { + library("CycloneDX Gradle Plugin", "1.10.0") { + group("org.cyclonedx") { modules = [ - "jakarta.inject-api" + "cyclonedx-gradle-plugin" ] } } @@ -151,6 +166,20 @@ bom { ] } } + library("OkHttp", "4.12.0") { + group("com.squareup.okhttp3") { + imports = [ + "okhttp-bom" + ] + } + } + library("OpenTelemetry Logback Appender", "2.7.0-alpha") { + group("io.opentelemetry.instrumentation") { + modules = [ + "opentelemetry-logback-appender-1.0" + ] + } + } library("Plexus Build API", "0.0.7") { group("org.sonatype.plexus") { modules = [ @@ -193,14 +222,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 9c4e2a97af63..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 @@ -175,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 @@ -187,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 @@ -229,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-jetty/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-jetty/build.gradle index 09e6cb4f19bd..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 @@ -13,8 +13,10 @@ dependencies { 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.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 b2f954082ed1..c6c7d32cebf5 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 @@ -23,6 +23,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 { @@ -147,11 +148,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') @@ -164,7 +182,7 @@ publishing.publications.withType(MavenPublication) { } } configuration { - delegate.mainClass('${start-class}') + delegate.mainClass('${spring-boot.run.main-class}') } } plugin { @@ -246,14 +264,6 @@ publishing.publications.withType(MavenPublication) { plugin { delegate.groupId('org.springframework.boot') delegate.artifactId('spring-boot-maven-plugin') - configuration { - image { - delegate.builder("paketobuildpacks/builder-jammy-tiny:latest"); - env { - delegate.BP_NATIVE_IMAGE("true") - } - } - } executions { execution { delegate.id('process-aot') @@ -268,9 +278,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 { @@ -314,9 +321,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-test/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle index e98d71281766..c5a856d8fb6e 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 @@ -24,3 +24,8 @@ dependencies { exclude group: "javax.xml.bind", module: "jaxb-api" } } + +checkRuntimeClasspathForConflicts { + ignore { name -> name.startsWith("mockito-extensions/") } +} + diff --git a/spring-boot-project/spring-boot-test-autoconfigure/build.gradle b/spring-boot-project/spring-boot-test-autoconfigure/build.gradle index 0a55d6a26ae7..09ab2bd8138f 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-test-autoconfigure/build.gradle @@ -12,17 +12,23 @@ dependencies { api(project(":spring-boot-project:spring-boot-test")) api(project(":spring-boot-project:spring-boot-autoconfigure")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-docker-compose")) dockerTestImplementation(project(":spring-boot-project:spring-boot-testcontainers")) dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("com.zaxxer:HikariCP") dockerTestImplementation("io.projectreactor:reactor-test") + dockerTestImplementation("com.redis:testcontainers-redis") + dockerTestImplementation("com.h2database:h2") dockerTestImplementation("org.assertj:assertj-core") dockerTestImplementation("org.junit.jupiter:junit-jupiter") + dockerTestImplementation("org.postgresql:postgresql") 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:postgresql") dockerTestImplementation("org.testcontainers:testcontainers") dockerTestRuntimeOnly("io.lettuce:lettuce-core") @@ -36,12 +42,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") } @@ -84,7 +90,8 @@ dependencies { testImplementation("com.fasterxml.jackson.module:jackson-module-parameter-names") testImplementation("com.h2database:h2") testImplementation("com.unboundid:unboundid-ldapsdk") - testImplementation("io.micrometer:micrometer-registry-prometheus") + testImplementation("io.lettuce:lettuce-core") + testImplementation("io.micrometer:micrometer-registry-prometheus-simpleclient") testImplementation("io.projectreactor.netty:reactor-netty-http") testImplementation("io.projectreactor:reactor-core") testImplementation("io.projectreactor:reactor-test") diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index 5fd24430d24e..5486ed745c12 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -21,7 +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.cassandra.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -56,7 +56,7 @@ class DataCassandraTestIntegrationTests { @Container @ServiceConnection - static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); @Autowired private CassandraTemplate cassandraTemplate; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index 1056a9ac24a2..0858b6ee3e5a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -21,7 +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.cassandra.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -53,7 +53,7 @@ class DataCassandraTestWithIncludeFilterIntegrationTests { @Container @ServiceConnection - static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); @Autowired private ExampleRepository exampleRepository; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/ldap/DataLdapTestDockerTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/ldap/DataLdapTestDockerTests.java new file mode 100644 index 000000000000..5ce48a457214 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/data/ldap/DataLdapTestDockerTests.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.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) +class DataLdapTestDockerTests { + + @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).singleElement().isEqualTo("example"); + } + + @Test + void serviceConnectionAutoConfigurationWasImported() { + assertThat(this.applicationContext).has(importedAutoConfiguration(ServiceConnectionAutoConfiguration.class)); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index 54b594cd6763..ec729ad4e2f7 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -34,8 +34,8 @@ public ExampleService(Neo4jTemplate neo4jTemplate) { this.neo4jTemplate = neo4jTemplate; } - public boolean hasNode(Class clazz) { - return this.neo4jTemplate.count(clazz) == 1; + public boolean hasNode(Class type) { + return this.neo4jTemplate.count(type) == 1; } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index 50ab4defe997..ed6a3be4cb2d 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -19,6 +19,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -27,7 +28,6 @@ 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.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.RedisOperations; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index 49c6f1a67a02..e4f43ca95b94 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -16,6 +16,7 @@ package org.springframework.boot.test.autoconfigure.data.redis; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; @@ -23,7 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testsupport.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.core.env.Environment; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index be773935ba6a..a32c477d971e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -19,6 +19,7 @@ import java.time.Duration; import java.util.UUID; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -27,7 +28,6 @@ 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.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.ReactiveRedisOperations; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 index 233511a441dc..5b5f7c810ecb 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/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 @@ -16,13 +16,13 @@ package org.springframework.boot.test.autoconfigure.data.redis; +import com.redis.testcontainers.RedisContainer; 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.testsupport.container.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseDockerComposeIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseDockerComposeIntegrationTests.java new file mode 100644 index 000000000000..79a23e4163b5 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseDockerComposeIntegrationTests.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.autoconfigure.jdbc; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; +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.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabaseDockerComposeIntegrationTests.SetupDockerCompose; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.support.TestPropertySourceUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link AutoConfigureTestDatabase} with Docker Compose. + * + * @author Phillip Webb + */ +@SpringBootTest +@ContextConfiguration(initializers = SetupDockerCompose.class) +@AutoConfigureTestDatabase +@OverrideAutoConfiguration(enabled = false) +@DisabledIfDockerUnavailable +class AutoConfigureTestDatabaseDockerComposeIntegrationTests { + + @Autowired + private DataSource dataSource; + + @Test + void dataSourceIsNotReplaced() { + assertThat(this.dataSource).isInstanceOf(HikariDataSource.class).isNotInstanceOf(EmbeddedDatabase.class); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class Config { + + } + + static class SetupDockerCompose implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + try { + Path composeFile = Files.createTempFile("", "-postgres-compose"); + String composeFileContent = new ClassPathResource("postgres-compose.yaml") + .getContentAsString(StandardCharsets.UTF_8) + .replace("{imageName}", TestImage.POSTGRESQL.toString()); + Files.writeString(composeFile, composeFileContent); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext, + "spring.docker.compose.skip.in-tests=false", "spring.docker.compose.stop.command=down", + "spring.docker.compose.file=" + composeFile.toAbsolutePath().toString()); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseDynamicPropertySourceIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseDynamicPropertySourceIntegrationTests.java new file mode 100644 index 000000000000..c18f27702ca8 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseDynamicPropertySourceIntegrationTests.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.test.autoconfigure.jdbc; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; +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.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link AutoConfigureTestDatabase} with Testcontainers and a + * {@link DynamicPropertySource @DynamicPropertySource}. + * + * @author Phillip Webb + */ +@SpringBootTest +@AutoConfigureTestDatabase +@Testcontainers(disabledWithoutDocker = true) +@OverrideAutoConfiguration(enabled = false) +class AutoConfigureTestDatabaseDynamicPropertySourceIntegrationTests { + + @Container + static PostgreSQLContainer postgres = TestImage.container(PostgreSQLContainer.class); + + @Autowired + private DataSource dataSource; + + @DynamicPropertySource + static void jdbcProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", postgres::getJdbcUrl); + } + + @Test + void dataSourceIsNotReplaced() { + assertThat(this.dataSource).isInstanceOf(HikariDataSource.class).isNotInstanceOf(EmbeddedDatabase.class); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseNonTestDatabaseIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseNonTestDatabaseIntegrationTests.java new file mode 100644 index 000000000000..463d47159dba --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseNonTestDatabaseIntegrationTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 javax.sql.DataSource; + +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabaseNonTestDatabaseIntegrationTests.SetupDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.support.TestPropertySourceUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link AutoConfigureTestDatabase} with Docker Compose. + * + * @author Phillip Webb + */ +@SpringBootTest +@ContextConfiguration(initializers = SetupDatabase.class) +@AutoConfigureTestDatabase +@OverrideAutoConfiguration(enabled = false) +@DisabledIfDockerUnavailable +class AutoConfigureTestDatabaseNonTestDatabaseIntegrationTests { + + @Container + static PostgreSQLContainer postgres = TestImage.container(PostgreSQLContainer.class); + + @Autowired + private DataSource dataSource; + + @Test + void dataSourceIsReplaced() { + assertThat(this.dataSource).isInstanceOf(EmbeddedDatabase.class); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class Config { + + } + + static class SetupDatabase implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + postgres.start(); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext, + "spring.datasource.url=" + postgres.getJdbcUrl()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseServiceConnectionIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseServiceConnectionIntegrationTests.java new file mode 100644 index 000000000000..d37448d2dcc4 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseServiceConnectionIntegrationTests.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.jdbc; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; +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.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link AutoConfigureTestDatabase} with Testcontainers and a + * {@link ServiceConnection @ServiceConnection}. + * + * @author Phillip Webb + */ +@SpringBootTest +@AutoConfigureTestDatabase(replace = Replace.NON_TEST) +@Testcontainers(disabledWithoutDocker = true) +@OverrideAutoConfiguration(enabled = false) +class AutoConfigureTestDatabaseServiceConnectionIntegrationTests { + + @Container + @ServiceConnection + static PostgreSQLContainer postgres = TestImage.container(PostgreSQLContainer.class); + + @Autowired + private DataSource dataSource; + + @Test + void dataSourceIsNotReplaced() { + assertThat(this.dataSource).isInstanceOf(HikariDataSource.class).isNotInstanceOf(EmbeddedDatabase.class); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseTestcontainersJdbcUrlIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseTestcontainersJdbcUrlIntegrationTests.java new file mode 100644 index 000000000000..c9771a947d0a --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabaseTestcontainersJdbcUrlIntegrationTests.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.test.autoconfigure.jdbc; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabaseTestcontainersJdbcUrlIntegrationTests.InitializeDatasourceUrl; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.support.TestPropertySourceUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link AutoConfigureTestDatabase} with Testcontainers and a + * {@link ServiceConnection @ServiceConnection}. + * + * @author Phillip Webb + */ +@SpringBootTest +@ContextConfiguration(initializers = InitializeDatasourceUrl.class) +@AutoConfigureTestDatabase(replace = Replace.NON_TEST) +@Testcontainers(disabledWithoutDocker = true) +@OverrideAutoConfiguration(enabled = false) +class AutoConfigureTestDatabaseTestcontainersJdbcUrlIntegrationTests { + + @Autowired + private DataSource dataSource; + + @Test + void dataSourceIsNotReplaced() { + assertThat(this.dataSource).isInstanceOf(HikariDataSource.class).isNotInstanceOf(EmbeddedDatabase.class); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class Config { + + } + + static class InitializeDatasourceUrl implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext, + "spring.datasource.url=jdbc:tc:postgis:" + TestImage.POSTGRESQL.getTag() + ":///"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/resources/postgres-compose.yaml b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/resources/postgres-compose.yaml new file mode 100644 index 000000000000..cb721c823b23 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/dockerTest/resources/postgres-compose.yaml @@ -0,0 +1,9 @@ +services: + database: + image: '{imageName}' + ports: + - '5432' + environment: + - 'POSTGRES_USER=myuser' + - 'POSTGRES_DB=mydatabase' + - 'POSTGRES_PASSWORD=secret' 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/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 746d0a747d90..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. @@ -30,7 +30,7 @@ * observability. *

* If this annotation is applied to a sliced test, an in-memory {@code MeterRegistry}, a - * no-op {@code Tracer} and an {@code ObservationRegistry} is added to the application + * no-op {@code Tracer} and an {@code ObservationRegistry} are added to the application * context. * * @author Moritz Halbritter diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabase.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabase.java index 9bed2317ed5a..4283a1ca8be5 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabase.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/AutoConfigureTestDatabase.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,10 +26,12 @@ import javax.sql.DataSource; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.container.ContainerImageMetadata; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; import org.springframework.boot.test.autoconfigure.properties.SkipPropertyMapping; import org.springframework.context.annotation.Primary; +import org.springframework.test.context.DynamicPropertySource; /** * Annotation that can be applied to a test class to configure a test database to use @@ -54,7 +56,7 @@ * @return the type of existing DataSource to replace */ @PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE) - Replace replace() default Replace.ANY; + Replace replace() default Replace.NON_TEST; /** * The type of connection to be established when {@link #replace() replacing} the @@ -69,6 +71,23 @@ */ enum Replace { + /** + * Replace the DataSource bean unless it is auto-configured and connecting to a + * test database. The following types of connections are considered test + * databases: + *

    + *
  • Any bean definition that includes {@link ContainerImageMetadata} (including + * {@code @ServiceConnection} annotated Testcontainer databases, and connections + * created using Docker Compose)
  • + *
  • Any connection configured using a {@code spring.datasource.url} backed by a + * {@link DynamicPropertySource @DynamicPropertySource}
  • + *
  • Any connection configured using a {@code spring.datasource.url} with the + * Testcontainers JDBC syntax
  • + *
+ * @since 3.4.0 + */ + NON_TEST, + /** * Replace the DataSource bean whether it was auto-configured or manually defined. */ diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java index c0c28e124cc3..cc59cbf970a4 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,9 @@ package org.springframework.boot.test.autoconfigure.jdbc; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.sql.DataSource; @@ -28,6 +30,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -37,8 +41,17 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.container.ContainerImageMetadata; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.bind.BoundPropertiesTrackingBindHandler; +import org.springframework.boot.context.properties.source.ConfigurationProperty; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.origin.PropertySourceOrigin; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Role; @@ -47,6 +60,8 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.type.MethodMetadata; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.util.Assert; @@ -63,25 +78,50 @@ public class TestDatabaseAutoConfiguration { @Bean - @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "AUTO_CONFIGURED") - @ConditionalOnMissingBean - public DataSource dataSource(Environment environment) { - return new EmbeddedDataSourceFactory(environment).getEmbeddedDatabase(); + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "NON_TEST", + matchIfMissing = true) + static EmbeddedDataSourceBeanFactoryPostProcessor nonTestEmbeddedDataSourceBeanFactoryPostProcessor( + Environment environment) { + return new EmbeddedDataSourceBeanFactoryPostProcessor(environment, Replace.NON_TEST); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "ANY", - matchIfMissing = true) - static EmbeddedDataSourceBeanFactoryPostProcessor embeddedDataSourceBeanFactoryPostProcessor() { - return new EmbeddedDataSourceBeanFactoryPostProcessor(); + @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "ANY") + static EmbeddedDataSourceBeanFactoryPostProcessor embeddedDataSourceBeanFactoryPostProcessor( + Environment environment) { + return new EmbeddedDataSourceBeanFactoryPostProcessor(environment, Replace.ANY); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "AUTO_CONFIGURED") + @ConditionalOnMissingBean + public DataSource dataSource(Environment environment) { + return new EmbeddedDataSourceFactory(environment).getEmbeddedDatabase(); } @Order(Ordered.LOWEST_PRECEDENCE) static class EmbeddedDataSourceBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { + private static final ConfigurationPropertyName DATASOURCE_URL_PROPERTY = ConfigurationPropertyName + .of("spring.datasource.url"); + + private static final Bindable BINDABLE_STRING = Bindable.of(String.class); + + private static final String DYNAMIC_VALUES_PROPERTY_SOURCE_CLASS = "org.springframework.test.context.support.DynamicValuesPropertySource"; + private static final Log logger = LogFactory.getLog(EmbeddedDataSourceBeanFactoryPostProcessor.class); + private final Environment environment; + + private final Replace replace; + + EmbeddedDataSourceBeanFactoryPostProcessor(Environment environment, Replace replace) { + this.environment = environment; + this.replace = replace; + } + @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (AotDetector.useGeneratedArtifacts()) { @@ -98,7 +138,7 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) private void process(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory) { BeanDefinitionHolder holder = getDataSourceBeanDefinition(beanFactory); - if (holder != null) { + if (holder != null && isReplaceable(beanFactory, holder)) { String beanName = holder.getBeanName(); boolean primary = holder.getBeanDefinition().isPrimary(); logger.info("Replacing '" + beanName + "' DataSource bean with " + (primary ? "primary " : "") @@ -135,6 +175,70 @@ private BeanDefinitionHolder getDataSourceBeanDefinition(ConfigurableListableBea return null; } + private boolean isReplaceable(ConfigurableListableBeanFactory beanFactory, BeanDefinitionHolder holder) { + if (this.replace == Replace.NON_TEST) { + return !isAutoConfigured(holder) || !isConnectingToTestDatabase(beanFactory); + } + return true; + } + + private boolean isAutoConfigured(BeanDefinitionHolder holder) { + if (holder.getBeanDefinition() instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { + MethodMetadata factoryMethodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata(); + return (factoryMethodMetadata != null) && (factoryMethodMetadata.getDeclaringClassName() + .startsWith("org.springframework.boot.autoconfigure.")); + } + return false; + } + + private boolean isConnectingToTestDatabase(ConfigurableListableBeanFactory beanFactory) { + return isUsingTestServiceConnection(beanFactory) || isUsingTestDatasourceUrl(); + } + + private boolean isUsingTestServiceConnection(ConfigurableListableBeanFactory beanFactory) { + for (String beanName : beanFactory.getBeanNamesForType(JdbcConnectionDetails.class)) { + try { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + if (ContainerImageMetadata.isPresent(beanDefinition)) { + return true; + } + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore + } + } + return false; + } + + private boolean isUsingTestDatasourceUrl() { + List bound = new ArrayList<>(); + Binder.get(this.environment, new BoundPropertiesTrackingBindHandler(bound::add)) + .bind(DATASOURCE_URL_PROPERTY, BINDABLE_STRING); + return !bound.isEmpty() && isUsingTestDatasourceUrl(bound.get(0)); + } + + private boolean isUsingTestDatasourceUrl(ConfigurationProperty configurationProperty) { + return isBoundToDynamicValuesPropertySource(configurationProperty) + || isTestcontainersUrl(configurationProperty); + } + + private boolean isBoundToDynamicValuesPropertySource(ConfigurationProperty configurationProperty) { + if (configurationProperty.getOrigin() instanceof PropertySourceOrigin origin) { + return isDynamicValuesPropertySource(origin.getPropertySource()); + } + return false; + } + + private boolean isDynamicValuesPropertySource(PropertySource propertySource) { + return propertySource != null + && DYNAMIC_VALUES_PROPERTY_SOURCE_CLASS.equals(propertySource.getClass().getName()); + } + + private boolean isTestcontainersUrl(ConfigurationProperty configurationProperty) { + Object value = configurationProperty.getValue(); + return (value != null) && value.toString().startsWith("jdbc:tc:"); + } + } static class EmbeddedDataSourceFactoryBean implements FactoryBean, EnvironmentAware, InitializingBean { 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 60cb5826de65..62c5628be700 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 @@ -77,7 +77,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/restdocs/RestDocumentationContextProviderRegistrar.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocumentationContextProviderRegistrar.java index 6a56125e81d3..92dd57e4770c 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocumentationContextProviderRegistrar.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocumentationContextProviderRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 +39,7 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B Map annotationAttributes = importingClassMetadata .getAnnotationAttributes(AutoConfigureRestDocs.class.getName()); BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(ManualRestDocumentation.class); + .rootBeanDefinition(ManualRestDocumentation.class); String outputDir = (String) annotationAttributes.get("outputDir"); if (StringUtils.hasText(outputDir)) { definitionBuilder.addConstructorArgValue(outputDir); 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/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 9e944e4fad3f..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,27 +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/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.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.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.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 cad2d5fb96fe..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,10 +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/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/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/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/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 5f1736e3a25f..2f81d64d4ee5 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/assertj/ApplicationContextAssertProvider.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java index f7fdf2122394..8542c5aa675c 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.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,12 +18,14 @@ import java.io.Closeable; import java.lang.reflect.Proxy; +import java.util.Arrays; import java.util.function.Supplier; import org.assertj.core.api.AssertProvider; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * An {@link ApplicationContext} that additionally supports AssertJ style assertions. Can @@ -101,16 +103,46 @@ public interface ApplicationContextAssertProvider * {@link ApplicationContext} or throw an exception if the context fails to start. * @return a {@link ApplicationContextAssertProvider} instance */ - @SuppressWarnings("unchecked") static , C extends ApplicationContext> T get(Class type, Class contextType, Supplier contextSupplier) { + return get(type, contextType, contextSupplier, new Class[0]); + } + + /** + * Factory method to create a new {@link ApplicationContextAssertProvider} instance. + * @param the assert provider type + * @param the context type + * @param type the type of {@link ApplicationContextAssertProvider} required (must be + * an interface) + * @param contextType the type of {@link ApplicationContext} being managed (must be an + * interface) + * @param contextSupplier a supplier that will either return a fully configured + * {@link ApplicationContext} or throw an exception if the context fails to start. + * @param additionalContextInterfaces and additional context interfaces to add to the + * proxy + * @return a {@link ApplicationContextAssertProvider} instance + * @since 3.4.0 + */ + @SuppressWarnings("unchecked") + static , C extends ApplicationContext> T get(Class type, + Class contextType, Supplier contextSupplier, + Class... additionalContextInterfaces) { Assert.notNull(type, "Type must not be null"); Assert.isTrue(type.isInterface(), "Type must be an interface"); Assert.notNull(contextType, "ContextType must not be null"); Assert.isTrue(contextType.isInterface(), "ContextType must be an interface"); - Class[] interfaces = { type, contextType }; + Class[] interfaces = merge(new Class[] { type, contextType }, additionalContextInterfaces); return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new AssertProviderApplicationContextInvocationHandler(contextType, contextSupplier)); } + private static Class[] merge(Class[] classes, Class[] additional) { + if (ObjectUtils.isEmpty(additional)) { + return classes; + } + Class[] result = Arrays.copyOf(classes, classes.length + additional.length); + System.arraycopy(additional, 0, result, classes.length, additional.length); + return result; + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableApplicationContext.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableApplicationContext.java index 9c2a4782f2b1..7cc7fa40b23c 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableApplicationContext.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableApplicationContext.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. @@ -49,4 +49,20 @@ static AssertableApplicationContext get(Supplier contextSupplier, + Class... additionalContextInterfaces) { + return ApplicationContextAssertProvider.get(AssertableApplicationContext.class, + ConfigurableApplicationContext.class, contextSupplier, additionalContextInterfaces); + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContext.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContext.java index 5af5a9fdd987..c18ec13eb6dc 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContext.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContext.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. @@ -51,4 +51,22 @@ static AssertableReactiveWebApplicationContext get( ConfigurableReactiveWebApplicationContext.class, contextSupplier); } + /** + * Factory method to create a new {@link AssertableReactiveWebApplicationContext} + * instance. + * @param contextSupplier a supplier that will either return a fully configured + * {@link ConfigurableReactiveWebApplicationContext} or throw an exception if the + * context fails to start. + * @param additionalContextInterfaces and additional context interfaces to add to the + * proxy + * @return a {@link AssertableReactiveWebApplicationContext} instance + * @since 3.4.0 + */ + static AssertableReactiveWebApplicationContext get( + Supplier contextSupplier, + Class... additionalContextInterfaces) { + return ApplicationContextAssertProvider.get(AssertableReactiveWebApplicationContext.class, + ConfigurableReactiveWebApplicationContext.class, contextSupplier, additionalContextInterfaces); + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContext.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContext.java index 96a7d03dd1b2..314b43503bd6 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContext.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContext.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. @@ -49,4 +49,20 @@ static AssertableWebApplicationContext get(Supplier contextSupplier, + Class... additionalContextInterfaces) { + return ApplicationContextAssertProvider.get(AssertableWebApplicationContext.class, + ConfigurableWebApplicationContext.class, contextSupplier, additionalContextInterfaces); + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java index 635222f8c749..6a7bd6672752 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,6 +106,8 @@ */ public abstract class AbstractApplicationContextRunner, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider> { + private static final Class[] NO_ADDITIONAL_CONTEXT_INTERFACES = {}; + private final RunnerConfiguration runnerConfiguration; private final Function, SELF> instanceFactory; @@ -115,13 +117,29 @@ public abstract class AbstractApplicationContextRunner contextFactory, Function, SELF> instanceFactory) { - Assert.notNull(contextFactory, "ContextFactory must not be null"); - Assert.notNull(contextFactory, "RunnerConfiguration must not be null"); - this.runnerConfiguration = new RunnerConfiguration<>(contextFactory); + this(instanceFactory, contextFactory, NO_ADDITIONAL_CONTEXT_INTERFACES); + } + + /** + * Create a new {@link AbstractApplicationContextRunner} instance. + * @param instanceFactory the factory used to create new instance of the runner + * @param contextFactory the factory used to create the actual context + * @param additionalContextInterfaces any additional application context interfaces to + * be added to the application context proxy + * @since 3.4.0 + */ + protected AbstractApplicationContextRunner(Function, SELF> instanceFactory, + Supplier contextFactory, Class... additionalContextInterfaces) { + Assert.notNull(instanceFactory, "'instanceFactory' must not be null"); + Assert.notNull(contextFactory, "'contextFactory' must not be null"); this.instanceFactory = instanceFactory; + this.runnerConfiguration = new RunnerConfiguration<>(contextFactory, additionalContextInterfaces); } /** @@ -386,7 +404,8 @@ private A createAssertableContext(boolean refresh) { ResolvableType resolvableType = ResolvableType.forClass(AbstractApplicationContextRunner.class, getClass()); Class assertType = (Class) resolvableType.resolveGeneric(1); Class contextType = (Class) resolvableType.resolveGeneric(2); - return ApplicationContextAssertProvider.get(assertType, contextType, () -> createAndLoadContext(refresh)); + return ApplicationContextAssertProvider.get(assertType, contextType, () -> createAndLoadContext(refresh), + this.runnerConfiguration.additionalContextInterfaces); } private C createAndLoadContext(boolean refresh) { @@ -472,6 +491,8 @@ protected static final class RunnerConfiguration contextFactory; + private final Class[] additionalContextInterfaces; + private boolean allowBeanDefinitionOverriding = false; private boolean allowCircularReferences = false; @@ -490,12 +511,14 @@ protected static final class RunnerConfiguration configurations = Collections.emptyList(); - private RunnerConfiguration(Supplier contextFactory) { + private RunnerConfiguration(Supplier contextFactory, Class[] additionalContextInterfaces) { this.contextFactory = contextFactory; + this.additionalContextInterfaces = additionalContextInterfaces; } private RunnerConfiguration(RunnerConfiguration source) { this.contextFactory = source.contextFactory; + this.additionalContextInterfaces = source.additionalContextInterfaces; this.allowBeanDefinitionOverriding = source.allowBeanDefinitionOverriding; this.allowCircularReferences = source.allowCircularReferences; this.initializers = source.initializers; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java index 83de2a500d7c..b7d87ed42a30 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ApplicationContextRunner.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,10 +47,24 @@ public ApplicationContextRunner() { /** * Create a new {@link ApplicationContextRunner} instance using the specified * {@code contextFactory} as the underlying source. - * @param contextFactory a supplier that returns a new instance on each call + * @param contextFactory a supplier that returns a new instance on each call be added + * to the application context proxy */ public ApplicationContextRunner(Supplier contextFactory) { - super(contextFactory, ApplicationContextRunner::new); + super(ApplicationContextRunner::new, contextFactory); + } + + /** + * Create a new {@link ApplicationContextRunner} instance using the specified + * {@code contextFactory} as the underlying source. + * @param contextFactory a supplier that returns a new instance on each call + * @param additionalContextInterfaces any additional application context interfaces to + * be added to the application context proxy + * @since 3.4.0 + */ + public ApplicationContextRunner(Supplier contextFactory, + Class... additionalContextInterfaces) { + super(ApplicationContextRunner::new, contextFactory, additionalContextInterfaces); } private ApplicationContextRunner(RunnerConfiguration runnerConfiguration) { diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java index 77c99ecc5b99..6274b01bf20f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunner.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,10 +47,25 @@ public ReactiveWebApplicationContextRunner() { /** * Create a new {@link ApplicationContextRunner} instance using the specified * {@code contextFactory} as the underlying source. - * @param contextFactory a supplier that returns a new instance on each call + * @param contextFactory a supplier that returns a new instance on each call be added + * to the application context proxy + * @since 3.4.0 */ public ReactiveWebApplicationContextRunner(Supplier contextFactory) { - super(contextFactory, ReactiveWebApplicationContextRunner::new); + super(ReactiveWebApplicationContextRunner::new, contextFactory); + } + + /** + * Create a new {@link ApplicationContextRunner} instance using the specified + * {@code contextFactory} as the underlying source. + * @param contextFactory a supplier that returns a new instance on each call + * @param additionalContextInterfaces any additional application context interfaces to + * be added to the application context proxy + * @since 3.4.0 + */ + public ReactiveWebApplicationContextRunner(Supplier contextFactory, + Class... additionalContextInterfaces) { + super(ReactiveWebApplicationContextRunner::new, contextFactory, additionalContextInterfaces); } private ReactiveWebApplicationContextRunner( diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java index fd186a747ac7..5528bdbb818d 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.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. @@ -51,10 +51,24 @@ public WebApplicationContextRunner() { /** * Create a new {@link WebApplicationContextRunner} instance using the specified * {@code contextFactory} as the underlying source. - * @param contextFactory a supplier that returns a new instance on each call + * @param contextFactory a supplier that returns a new instance on each call be added + * to the application context proxy */ public WebApplicationContextRunner(Supplier contextFactory) { - super(contextFactory, WebApplicationContextRunner::new); + super(WebApplicationContextRunner::new, contextFactory); + } + + /** + * Create a new {@link WebApplicationContextRunner} instance using the specified + * {@code contextFactory} as the underlying source. + * @param contextFactory a supplier that returns a new instance on each call + * @param additionalContextInterfaces any additional application context interfaces to + * be added to the application context proxy + * @since 3.4.0 + */ + public WebApplicationContextRunner(Supplier contextFactory, + Class... additionalContextInterfaces) { + super(WebApplicationContextRunner::new, contextFactory, additionalContextInterfaces); } private WebApplicationContextRunner(RunnerConfiguration configuration) { 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..6048fc1da85e 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. @@ -23,7 +23,10 @@ * * @author Phillip Webb * @see DefinitionsParser + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..320c2b0637b6 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. @@ -41,7 +41,10 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class DefinitionsParser { private final Set definitions; @@ -112,8 +115,8 @@ private void addDefinition(AnnotatedElement element, Definition definition, Stri private Set getOrDeduceTypes(AnnotatedElement element, Class[] value, Class source) { Set types = new LinkedHashSet<>(); - for (Class clazz : value) { - types.add(ResolvableType.forClass(clazz)); + for (Class type : value) { + types.add(ResolvableType.forClass(type)); } if (types.isEmpty() && element instanceof Field field) { types.add((field.getGenericType() instanceof TypeVariable) ? ResolvableType.forField(field, source) 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..416e1deb9142 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. @@ -37,7 +37,10 @@ * A complete definition that can be used to create a Mockito mock. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..ad6f70474a55 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. @@ -28,7 +28,10 @@ * A {@link ContextCustomizer} to add Mockito support. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..9946ddfb1be2 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. @@ -27,7 +27,10 @@ * A {@link ContextCustomizerFactory} to add Mockito support. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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 9bde5b2a14b5..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 @@ -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 { @@ -420,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..941b8784031f 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 @@ -27,6 +27,8 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.util.ReflectionUtils; @@ -46,7 +48,11 @@ * @author Moritz Halbritter * @since 1.4.2 * @see ResetMocksTestExecutionListener + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of Spring Framework's support for + * {@link MockitoBean} and {@link MockitoSpyBean}. */ +@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"; 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..9f46416ac3a5 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. @@ -34,7 +34,10 @@ * @author Phillip Webb * @author Stephane Nicoll * @see Definition + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..5a9bb64f5b3e 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. @@ -37,7 +37,10 @@ * A complete definition that can be used to create a Mockito spy. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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/web/client/TestRestTemplate.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java index a0f975d3f43b..a8d7cf2e9390 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java @@ -20,6 +20,7 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.Arrays; import java.util.HashSet; @@ -37,15 +38,15 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.protocol.HttpClientContext; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; -import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; +import org.apache.hc.client5.http.ssl.TlsSocketStrategy; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.TrustStrategy; -import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RootUriTemplateHandler; import org.springframework.core.ParameterizedTypeReference; @@ -145,7 +146,7 @@ public TestRestTemplate(RestTemplateBuilder builder, String username, String pas if (httpClientOptions != null) { ClientHttpRequestFactory requestFactory = builder.buildRequestFactory(); if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) { - builder = builder.requestFactory( + builder = builder.requestFactoryBuilder( (settings) -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions, settings)); } } @@ -991,7 +992,7 @@ public enum HttpClientOption { ENABLE_REDIRECTS, /** - * Use a {@link SSLConnectionSocketFactory} with {@link TrustSelfSignedStrategy}. + * Use a {@link TlsSocketStrategy} that trusts self-signed certificates. */ SSL @@ -1006,6 +1007,26 @@ protected static class CustomHttpComponentsClientHttpRequestFactory extends Http private final boolean enableRedirects; + /** + * Create a new {@link CustomHttpComponentsClientHttpRequestFactory} instance. + * @param httpClientOptions the {@link HttpClient} options + * @param settings the settings to apply + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link #CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[], ClientHttpRequestFactorySettings)} + */ + @Deprecated(since = "3.4.0", forRemoval = true) + @SuppressWarnings("removal") + public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions, + org.springframework.boot.web.client.ClientHttpRequestFactorySettings settings) { + this(httpClientOptions, new ClientHttpRequestFactorySettings(null, settings.connectTimeout(), + settings.readTimeout(), settings.sslBundle())); + } + + /** + * Create a new {@link CustomHttpComponentsClientHttpRequestFactory} instance. + * @param httpClientOptions the {@link HttpClient} options + * @param settings the settings to apply + */ public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions, ClientHttpRequestFactorySettings settings) { Set options = new HashSet<>(Arrays.asList(httpClientOptions)); @@ -1037,7 +1058,7 @@ private PoolingHttpClientConnectionManager createConnectionManager(Duration read throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create(); if (ssl) { - builder.setSSLSocketFactory(createSocketFactory()); + builder.setTlsSocketStrategy(createTlsSocketStrategy()); } if (readTimeout != null) { SocketConfig socketConfig = SocketConfig.custom() @@ -1048,14 +1069,12 @@ private PoolingHttpClientConnectionManager createConnectionManager(Duration read return builder.build(); } - private SSLConnectionSocketFactory createSocketFactory() - throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + private TlsSocketStrategy createTlsSocketStrategy() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()) .build(); - return SSLConnectionSocketFactoryBuilder.create() - .setSslContext(sslContext) - .setTlsVersions(TLS.V_1_3, TLS.V_1_2) - .build(); + return new DefaultClientTlsStrategy(sslContext, new String[] { TLS.V_1_3.getId(), TLS.V_1_2.getId() }, null, + null, null); } @Override @@ -1075,4 +1094,13 @@ protected RequestConfig createRequestConfig() { } + private static final class TrustSelfSignedStrategy implements TrustStrategy { + + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) { + return chain.length == 1; + } + + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClient.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClient.java index 23ba2b19eabb..47c46b477f45 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClient.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/LocalHostWebClient.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. @@ -18,9 +18,9 @@ import java.io.IOException; -import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; -import com.gargoylesoftware.htmlunit.Page; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.FailingHttpStatusCodeException; +import org.htmlunit.Page; +import org.htmlunit.WebClient; import org.springframework.core.env.Environment; import org.springframework.util.Assert; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriver.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriver.java index f38c8ade7927..60d0f7776d51 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriver.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriver.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,7 +16,7 @@ package org.springframework.boot.test.web.htmlunit.webdriver; -import com.gargoylesoftware.htmlunit.BrowserVersion; +import org.htmlunit.BrowserVersion; import org.openqa.selenium.Capabilities; import org.springframework.core.env.Environment; 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 f9cc7f6e69ca..3f6a4888d110 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 @@ -160,11 +160,11 @@ void propertySourceOrdering() { .stream() .map(PropertySource::getName) .collect(Collectors.toCollection(ArrayList::new)); - String last = names.remove(names.size() - 1); + String configResource = names.remove(names.size() - 2); assertThat(names).containsExactly("configurationProperties", "Inlined Test Properties", "commandLineArgs", "servletConfigInitParams", "servletContextInitParams", "systemProperties", "systemEnvironment", - "random"); - assertThat(last).startsWith("Config resource"); + "random", "applicationInfo"); + assertThat(configResource).startsWith("Config resource"); } @Test diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AdditionalContextInterface.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AdditionalContextInterface.java new file mode 100644 index 000000000000..0144ffc6164e --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AdditionalContextInterface.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.test.context.assertj; + +import org.springframework.context.ApplicationContext; + +/** + * Tests extra interface that can be applied to an {@link ApplicationContext} + * + * @author Phillip Webb + */ +interface AdditionalContextInterface { + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableApplicationContextTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableApplicationContextTests.java index 91189ce2a99c..3ff56807dd49 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableApplicationContextTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableApplicationContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; /** * Tests for {@link AssertableApplicationContext}. @@ -38,4 +39,14 @@ void getShouldReturnProxy() { assertThat(context).isInstanceOf(ConfigurableApplicationContext.class); } + @Test + void getWhenHasAdditionalInterfaceShouldReturnProxy() { + AssertableApplicationContext context = AssertableApplicationContext.get( + () -> mock(ConfigurableApplicationContext.class, + withSettings().extraInterfaces(AdditionalContextInterface.class)), + AdditionalContextInterface.class); + assertThat(context).isInstanceOf(ConfigurableApplicationContext.class) + .isInstanceOf(AdditionalContextInterface.class); + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContextTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContextTests.java index b84b776bbd35..0947f9b80eaf 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContextTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableReactiveWebApplicationContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; /** * Tests for {@link AssertableReactiveWebApplicationContext}. @@ -38,4 +39,14 @@ void getShouldReturnProxy() { assertThat(context).isInstanceOf(ConfigurableReactiveWebApplicationContext.class); } + @Test + void getWhenHasAdditionalInterfaceShouldReturnProxy() { + AssertableReactiveWebApplicationContext context = AssertableReactiveWebApplicationContext.get( + () -> mock(ConfigurableReactiveWebApplicationContext.class, + withSettings().extraInterfaces(AdditionalContextInterface.class)), + AdditionalContextInterface.class); + assertThat(context).isInstanceOf(ConfigurableReactiveWebApplicationContext.class) + .isInstanceOf(AdditionalContextInterface.class); + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContextTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContextTests.java index 53873f9c47ad..1a2268dbc0a8 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContextTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/AssertableWebApplicationContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; /** * Tests for {@link AssertableWebApplicationContext}. @@ -38,4 +39,14 @@ void getShouldReturnProxy() { assertThat(context).isInstanceOf(ConfigurableWebApplicationContext.class); } + @Test + void getWhenHasAdditionalInterfaceShouldReturnProxy() { + ConfigurableWebApplicationContext context = AssertableWebApplicationContext.get( + () -> mock(ConfigurableWebApplicationContext.class, + withSettings().extraInterfaces(AdditionalContextInterface.class)), + AdditionalContextInterface.class); + assertThat(context).isInstanceOf(ConfigurableWebApplicationContext.class) + .isInstanceOf(AdditionalContextInterface.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/context/runner/AbstractApplicationContextRunnerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java index cd309da53698..d4a38ce5deb4 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -258,8 +258,16 @@ void prepareDoesNotRefreshContext() { }); } + @Test + void getWirhAdditionalContextInterfaceHasCorrectInstanceOf() { + getWithAdditionalContextInterface() + .run((context) -> assertThat(context).isInstanceOf(AdditionalContextInterface.class)); + } + protected abstract T get(); + protected abstract T getWithAdditionalContextInterface(); + private static void throwCheckedException(String message) throws IOException { throw new IOException(message); } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AdditionalContextInterface.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AdditionalContextInterface.java new file mode 100644 index 000000000000..b66a01715b8e --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AdditionalContextInterface.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.test.context.runner; + +import org.springframework.context.ApplicationContext; + +/** + * Tests extra interface that can be applied to an {@link ApplicationContext} + * + * @author Phillip Webb + */ +interface AdditionalContextInterface { + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ApplicationContextRunnerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ApplicationContextRunnerTests.java index 462d58b9a546..86966fccb739 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ApplicationContextRunnerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ApplicationContextRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Tests for {@link ApplicationContextRunner}. @@ -33,4 +34,15 @@ protected ApplicationContextRunner get() { return new ApplicationContextRunner(); } + @Override + protected ApplicationContextRunner getWithAdditionalContextInterface() { + return new ApplicationContextRunner(TestAnnotationConfigApplicationContext::new, + AdditionalContextInterface.class); + } + + static class TestAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext + implements AdditionalContextInterface { + + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunnerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunnerTests.java index 6d859b7d7891..cf7a66f901db 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunnerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/ReactiveWebApplicationContextRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.test.context.runner; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext; /** @@ -33,4 +34,15 @@ protected ReactiveWebApplicationContextRunner get() { return new ReactiveWebApplicationContextRunner(); } + @Override + protected ReactiveWebApplicationContextRunner getWithAdditionalContextInterface() { + return new ReactiveWebApplicationContextRunner(TestAnnotationConfigReactiveWebApplicationContext::new, + AdditionalContextInterface.class); + } + + static class TestAnnotationConfigReactiveWebApplicationContext extends AnnotationConfigReactiveWebApplicationContext + implements AdditionalContextInterface { + + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/WebApplicationContextRunnerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/WebApplicationContextRunnerTests.java index edf448ff07b0..8d2ede8f98a6 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/WebApplicationContextRunnerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/WebApplicationContextRunnerTests.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,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.ConfigurableWebApplicationContext; @@ -43,4 +44,15 @@ protected WebApplicationContextRunner get() { return new WebApplicationContextRunner(); } + @Override + protected WebApplicationContextRunner getWithAdditionalContextInterface() { + return new WebApplicationContextRunner(TestAnnotationConfigServletWebApplicationContext::new, + AdditionalContextInterface.class); + } + + static class TestAnnotationConfigServletWebApplicationContext extends AnnotationConfigServletWebApplicationContext + implements AdditionalContextInterface { + + } + } 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..539d5eeb6f2a 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. @@ -20,7 +20,10 @@ * Concrete implementation of {@link AbstractMockBeanOnGenericTests}. * * @author Madhura Bhave + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..1d01a90ee065 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. @@ -31,7 +31,10 @@ * @param type of thing * @param type of something * @author Madhura Bhave + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..0758d11d9cc9 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. @@ -36,7 +36,10 @@ * Tests for {@link DefinitionsParser}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class DefinitionsParserTests { private final DefinitionsParser parser = new DefinitionsParser(); @@ -190,22 +193,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 +222,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 +241,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 +275,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 +294,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..6251f2ccb3a9 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. @@ -42,7 +42,10 @@ * Tests for application context caching when using {@link MockBean @MockBean}. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..bab9e5859184 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. @@ -34,7 +34,10 @@ * Test {@link MockBean @MockBean} for a factory bean. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..cd016b7757aa 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. @@ -35,7 +35,10 @@ * existing beans. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockBeanOnConfigurationClassForExistingBeanIntegrationTests { @@ -48,6 +51,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..8b537cf5d6a2 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. @@ -34,8 +34,11 @@ * instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") @ExtendWith(SpringExtension.class) +@Deprecated(since = "3.4.0", forRemoval = true) class MockBeanOnConfigurationClassForNewBeanIntegrationTests { @Autowired @@ -47,6 +50,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..2558a095e721 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. @@ -35,8 +35,11 @@ * used to replace existing beans. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..6e95d7a081f9 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. @@ -34,8 +34,11 @@ * used to inject new mock instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..ba5fad0a6e8f 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. @@ -20,10 +20,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBeanOnContextHierarchyIntegrationTests.ChildConfig; -import org.springframework.boot.test.mock.mockito.MockBeanOnContextHierarchyIntegrationTests.ParentConfig; -import org.springframework.boot.test.mock.mockito.example.ExampleService; -import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; @@ -38,10 +34,13 @@ * {@link ContextHierarchy @ContextHierarchy}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) -@ContextHierarchy({ @ContextConfiguration(classes = ParentConfig.class), - @ContextConfiguration(classes = ChildConfig.class) }) +@ContextHierarchy({ @ContextConfiguration(classes = MockBeanOnContextHierarchyIntegrationTests.ParentConfig.class), + @ContextConfiguration(classes = MockBeanOnContextHierarchyIntegrationTests.ChildConfig.class) }) class MockBeanOnContextHierarchyIntegrationTests { @Autowired @@ -51,22 +50,30 @@ class MockBeanOnContextHierarchyIntegrationTests { void testMocking() { ApplicationContext context = this.childConfig.getContext(); ApplicationContext parentContext = context.getParent(); - assertThat(parentContext.getBeanNamesForType(ExampleService.class)).hasSize(1); - assertThat(parentContext.getBeanNamesForType(ExampleServiceCaller.class)).isEmpty(); - assertThat(context.getBeanNamesForType(ExampleService.class)).isEmpty(); - assertThat(context.getBeanNamesForType(ExampleServiceCaller.class)).hasSize(1); - assertThat(context.getBean(ExampleService.class)).isNotNull(); - assertThat(context.getBean(ExampleServiceCaller.class)).isNotNull(); + assertThat(parentContext + .getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleService.class)).hasSize(1); + assertThat(parentContext + .getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class)) + .isEmpty(); + assertThat(context.getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleService.class)) + .isEmpty(); + assertThat(context + .getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class)) + .hasSize(1); + assertThat(context.getBean(org.springframework.boot.test.mock.mockito.example.ExampleService.class)) + .isNotNull(); + assertThat(context.getBean(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class)) + .isNotNull(); } @Configuration(proxyBeanMethods = false) - @MockBean(ExampleService.class) + @MockBean(org.springframework.boot.test.mock.mockito.example.ExampleService.class) static class ParentConfig { } @Configuration(proxyBeanMethods = false) - @MockBean(ExampleServiceCaller.class) + @MockBean(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class) static class ChildConfig implements ApplicationContextAware { private ApplicationContext context; 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..0ba686e67211 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. @@ -38,7 +38,10 @@ * * @author Phillip Webb * @see gh-5724 + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..6051d06a1910 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. @@ -34,7 +34,10 @@ * Test {@link MockBean @MockBean} on a test class can be used to replace existing beans. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..9d14d1b7411a 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. @@ -34,7 +34,10 @@ * instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..6e8bbc80a199 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. @@ -36,7 +36,10 @@ * * @author Phillip Webb * @see MockBeanOnTestFieldForExistingBeanIntegrationTests + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..1210206ad095 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. @@ -27,7 +27,10 @@ * config to trigger caching. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..1b9416c18fce 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. @@ -34,7 +34,10 @@ * * @author Phillip Webb * @see MockBeanOnTestFieldForExistingBeanCacheIntegrationTests + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..d2cb657f2751 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. @@ -39,7 +39,10 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..b534ccc743af 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. @@ -34,7 +34,10 @@ * instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..a70788e23c6b 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. @@ -45,7 +45,10 @@ * * @author Phillip Webb * @see 5837 + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..4d062f934d9b 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. @@ -33,7 +33,10 @@ * Tests for a mock bean where the mocked interface has an async method. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..a3a68c1d3090 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. @@ -36,7 +36,10 @@ * {@link DirtiesContext @DirtiesContext} and {@link ClassMode#BEFORE_EACH_TEST_METHOD}. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..8553b5cddebb 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. @@ -34,7 +34,10 @@ * instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..7ff0d6e082a9 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. @@ -31,7 +31,10 @@ * Tests for a mock bean where the class being mocked uses field injection. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..43576ada919d 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. @@ -30,7 +30,10 @@ * * @author Andy Wilkinson * @see gh-27693 + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..f349e9e8a46e 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. @@ -33,7 +33,10 @@ * Tests for {@link MockDefinition}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..a2cddb61a788 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. @@ -28,7 +28,10 @@ * Tests for {@link MockReset}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..4081af4e36f2 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. @@ -26,7 +26,10 @@ * Tests for {@link MockitoContextCustomizerFactory}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoContextCustomizerFactoryTests { private final MockitoContextCustomizerFactory factory = new MockitoContextCustomizerFactory(); @@ -60,16 +63,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..203b4288c311 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. @@ -33,7 +33,10 @@ * Tests for {@link MockitoContextCustomizer}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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 5bb4bace7047..e492abc0e90d 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. @@ -50,7 +50,10 @@ * @author Andy Wilkinson * @author Andreas Neiser * @author Madhura Bhave + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class MockitoPostProcessorTests { @Test 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..7804ffdecd7d 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 @@ -49,7 +49,10 @@ * Integration tests for {@link MockitoTestExecutionListener}. * * @author Moritz Halbritter + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class MockitoTestExecutionListenerIntegrationTests { @@ -233,6 +236,7 @@ void shouldNotBeAffectedByOtherTests() { @Nested @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestInstance(Lifecycle.PER_CLASS) + @Disabled("https://github.com/spring-projects/spring-framework/issues/33690") class ConfigureMockInBeforeAll { @Mock 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..09dec0c271c4 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. @@ -41,7 +41,10 @@ * Tests for {@link MockitoTestExecutionListener}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(MockitoExtension.class) class MockitoTestExecutionListenerTests { 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..4b7fdeaffaa6 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. @@ -40,7 +40,10 @@ * Tests for {@link QualifierDefinition}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..e985dbe17742 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. @@ -39,7 +39,10 @@ * * @author Phillip Webb * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..04990b5c5292 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. @@ -16,8 +16,10 @@ package org.springframework.boot.test.mock.mockito; +import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import org.mockito.internal.configuration.plugins.Plugins; +import org.mockito.plugins.MockResolver; import static org.assertj.core.api.Assertions.assertThat; @@ -25,12 +27,16 @@ * Integration tests for {@link SpringBootMockResolver}. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class SpringBootMockResolverIntegrationTests { @Test void customMockResolverIsRegisteredWithMockito() { - assertThat(Plugins.getMockResolvers()).hasOnlyElementsOfType(SpringBootMockResolver.class); + assertThat(Plugins.getMockResolvers()).haveAtLeastOne(new Condition( + SpringBootMockResolver.class::isInstance, "Spring Boot mock resolver instance")); } } 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..138bf78ae132 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 @@ -29,7 +29,10 @@ * Tests for {@link SpringBootMockResolver}. * * @author Moritz Halbritter + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..2b424acac20a 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. @@ -34,7 +34,10 @@ * beans. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnConfigurationClassForExistingBeanIntegrationTests { @@ -47,6 +50,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..10049592428a 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. @@ -34,7 +34,10 @@ * instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnConfigurationClassForNewBeanIntegrationTests { @@ -47,6 +50,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..ed0c862b6735 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. @@ -35,7 +35,10 @@ * to replace existing beans. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..9a85a37b3c2d 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. @@ -34,7 +34,10 @@ * to inject new spy instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..b9763345da07 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. @@ -20,11 +20,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBeanOnContextHierarchyIntegrationTests.ChildConfig; -import org.springframework.boot.test.mock.mockito.SpyBeanOnContextHierarchyIntegrationTests.ParentConfig; -import org.springframework.boot.test.mock.mockito.example.ExampleService; -import org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller; -import org.springframework.boot.test.mock.mockito.example.SimpleExampleService; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; @@ -39,10 +34,13 @@ * {@link ContextHierarchy @ContextHierarchy}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) -@ContextHierarchy({ @ContextConfiguration(classes = ParentConfig.class), - @ContextConfiguration(classes = ChildConfig.class) }) +@ContextHierarchy({ @ContextConfiguration(classes = SpyBeanOnContextHierarchyIntegrationTests.ParentConfig.class), + @ContextConfiguration(classes = SpyBeanOnContextHierarchyIntegrationTests.ChildConfig.class) }) class SpyBeanOnContextHierarchyIntegrationTests { @Autowired @@ -52,22 +50,30 @@ class SpyBeanOnContextHierarchyIntegrationTests { void testSpying() { ApplicationContext context = this.childConfig.getContext(); ApplicationContext parentContext = context.getParent(); - assertThat(parentContext.getBeanNamesForType(ExampleService.class)).hasSize(1); - assertThat(parentContext.getBeanNamesForType(ExampleServiceCaller.class)).isEmpty(); - assertThat(context.getBeanNamesForType(ExampleService.class)).isEmpty(); - assertThat(context.getBeanNamesForType(ExampleServiceCaller.class)).hasSize(1); - assertThat(context.getBean(ExampleService.class)).isNotNull(); - assertThat(context.getBean(ExampleServiceCaller.class)).isNotNull(); + assertThat(parentContext + .getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleService.class)).hasSize(1); + assertThat(parentContext + .getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class)) + .isEmpty(); + assertThat(context.getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleService.class)) + .isEmpty(); + assertThat(context + .getBeanNamesForType(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class)) + .hasSize(1); + assertThat(context.getBean(org.springframework.boot.test.mock.mockito.example.ExampleService.class)) + .isNotNull(); + assertThat(context.getBean(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class)) + .isNotNull(); } @Configuration(proxyBeanMethods = false) - @SpyBean(SimpleExampleService.class) + @SpyBean(org.springframework.boot.test.mock.mockito.example.SimpleExampleService.class) static class ParentConfig { } @Configuration(proxyBeanMethods = false) - @SpyBean(ExampleServiceCaller.class) + @SpyBean(org.springframework.boot.test.mock.mockito.example.ExampleServiceCaller.class) static class ChildConfig implements ApplicationContextAware { private ApplicationContext context; 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..c3db640893a5 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. @@ -33,7 +33,10 @@ * Test {@link SpyBean @SpyBean} on a test class can be used to replace existing beans. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..90bc40ea7040 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. @@ -33,7 +33,10 @@ * Test {@link SpyBean @SpyBean} on a test class can be used to inject new spy instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..77139b368083 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. @@ -36,7 +36,10 @@ * * @author Phillip Webb * @see SpyBeanOnTestFieldForExistingBeanIntegrationTests + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..384fb4fb26a9 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. @@ -27,7 +27,10 @@ * config to trigger caching. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..a1a0050986cb 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. @@ -34,7 +34,10 @@ * * @author Phillip Webb * @see SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..ce3de45b7056 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. @@ -38,7 +38,10 @@ * bean while preserving qualifiers. * * @author Andreas Neiser + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..2a933eada63d 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. @@ -20,7 +20,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests.SpyBeanOnTestFieldForExistingCircularBeansConfig; import org.springframework.context.annotation.Import; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -32,9 +31,13 @@ * beans with circular dependencies. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = SpyBeanOnTestFieldForExistingCircularBeansConfig.class) +@ContextConfiguration( + classes = SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests.SpyBeanOnTestFieldForExistingCircularBeansConfig.class) class SpyBeanOnTestFieldForExistingCircularBeansIntegrationTests { @SpyBean 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..48a4b244abf3 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. @@ -38,7 +38,10 @@ * * @author Phillip Webb * @see SpyBeanOnTestFieldForExistingBeanCacheIntegrationTests + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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 index 82b5aaad78bb..338465332a03 100644 --- 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 @@ -39,7 +39,10 @@ * bean with generics that's produced by a factory bean. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) @ExtendWith(SpringExtension.class) class SpyBeanOnTestFieldForExistingGenericBeanProducedByFactoryBeanIntegrationTests { 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..538925c6f23c 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. @@ -37,7 +37,10 @@ * instance when there are multiple candidates and one is primary. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..f1c2f51df381 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. @@ -34,7 +34,10 @@ * instances. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..aaaee7f7ac4e 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. @@ -43,7 +43,10 @@ * * @author Phillip Webb * @see 5837 + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..edeb01bdf4fc 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. @@ -43,7 +43,10 @@ * * @author Phillip Webb * @see 5837 + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..ca495fcbc213 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. @@ -35,7 +35,10 @@ * {@link DirtiesContext @DirtiesContext} and {@link ClassMode#BEFORE_EACH_TEST_METHOD}. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..62a3c2874f7e 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. @@ -34,7 +34,10 @@ * Tests for {@link SpyBean @SpyBean} with a JDK proxy. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..518b2ac83692 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. @@ -34,7 +34,10 @@ * * @author Phillip Webb * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..8f5cea4d6f08 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. @@ -34,7 +34,10 @@ * Tests for {@link SpyDefinition}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..9308e6940e30 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. @@ -25,7 +25,9 @@ * Custom qualifier for testing. * * @author Stephane Nicoll + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..d2505f0f1983 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. @@ -20,7 +20,10 @@ * An {@link ExampleService} that uses a custom qualifier. * * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..e2577ae2850e 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. @@ -20,7 +20,9 @@ * Example extra interface for mocking tests. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..5f9efa1b2d07 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. @@ -21,7 +21,9 @@ * * @param the generic type * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..9a1c396c13e0 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. @@ -20,7 +20,10 @@ * Example bean for mocking tests that calls {@link ExampleGenericService}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..859d3e1a2e1e 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. @@ -20,7 +20,10 @@ * Example bean for mocking tests that calls {@link ExampleGenericService}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..15089baeda00 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. @@ -20,7 +20,9 @@ * Example service interface for mocking tests. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..36b3b06b8a8f 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. @@ -20,7 +20,10 @@ * Example bean for mocking tests that calls {@link ExampleService}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..9a96c3565e2f 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. @@ -22,7 +22,10 @@ * An {@link ExampleService} that always throws an exception. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..56d3674c0350 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. @@ -20,7 +20,10 @@ * Example service implementation for spy tests. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..0aa5b9e359b4 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. @@ -20,7 +20,10 @@ * Example generic service implementation for spy tests. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..4ddb53e601b8 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. @@ -20,7 +20,10 @@ * Example service implementation for spy tests. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..a89b6cb1c671 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. @@ -20,7 +20,10 @@ * Example generic service implementation for spy tests. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@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..3f0bc86b84e9 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,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/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-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index a590b5832ef0..d175c0bdf227 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -20,12 +20,18 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } dockerTestImplementation("com.couchbase.client:java-client") - dockerTestImplementation("com.datastax.oss:java-driver-core") + dockerTestImplementation("com.hazelcast:hazelcast") 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-jakarta") + 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") @@ -42,21 +48,26 @@ dependencies { 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:clickhouse") optional("org.testcontainers:couchbase") optional("org.testcontainers:elasticsearch") + optional("org.testcontainers:grafana") optional("org.testcontainers:jdbc") optional("org.testcontainers:kafka") optional("org.testcontainers:mariadb") @@ -71,10 +82,22 @@ dependencies { optional("org.testcontainers:rabbitmq") optional("org.testcontainers:redpanda") optional("org.testcontainers:r2dbc") + optional("com.redis:testcontainers-redis") + optional("com.hazelcast:hazelcast") testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("org.assertj:assertj-core") testImplementation("org.junit.jupiter:junit-jupiter") 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") } diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java index c3d0bd43703b..0ca80cbb01c2 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java @@ -103,7 +103,7 @@ void importWhenHasNonStaticContainerFieldThrowsException() { void importWhenHasContainerDefinitionsWithDynamicPropertySource() { this.applicationContext = new AnnotationConfigApplicationContext( ContainerDefinitionsWithDynamicPropertySource.class); - assertThat(this.applicationContext.getEnvironment().containsProperty("container.port")).isTrue(); + assertThat(this.applicationContext.getEnvironment().getProperty("container.port")).isNotNull(); } @Test diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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 index f4f912f84c24..3a5a65180963 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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 @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; @@ -30,7 +31,6 @@ import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleOrderIntegrationTests.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.Bean; import org.springframework.context.annotation.Configuration; @@ -63,7 +63,7 @@ void eventsAreOrderedCorrectlyAfterStartup() { static class ContainerConfig { @Bean - @ServiceConnection("redis") + @ServiceConnection RedisContainer redisContainer() { return TestImage.container(EventRecordingRedisContainer.class); } 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 index 48614af9f472..a223476b9e09 100644 --- 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 @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; @@ -34,7 +35,6 @@ 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; @@ -74,7 +74,7 @@ static class ContainerConfig { @Bean @Scope("custom") - @ServiceConnection("redis") + @ServiceConnection RedisContainer redisContainer() { return TestImage.container(EventRecordingRedisContainer.class); } 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 index 0461375186b1..10ddb2f93f09 100644 --- 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 @@ -19,7 +19,9 @@ import java.util.ArrayList; import java.util.List; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -27,12 +29,14 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; 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.boot.testsupport.system.CapturedOutput; +import org.springframework.boot.testsupport.system.OutputCaptureExtension; 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.DynamicPropertyRegistrar; import org.springframework.test.context.DynamicPropertyRegistry; import static org.assertj.core.api.Assertions.assertThat; @@ -41,8 +45,10 @@ * Tests for {@link TestcontainersPropertySourceAutoConfiguration}. * * @author Phillip Webb + * @author Andy Wilkinson */ @DisabledIfDockerUnavailable +@ExtendWith(OutputCaptureExtension.class) class TestcontainersPropertySourceAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -51,19 +57,66 @@ class TestcontainersPropertySourceAutoConfigurationTests { @Test @SuppressWarnings("removal") - void containerBeanMethodContributesProperties() { - List events = new ArrayList<>(); + @Deprecated(since = "3.4.0", forRemoval = true) + void registeringADynamicPropertyFailsByDefault() { this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class) + .run((context) -> assertThat(context).getFailure() + .rootCause() + .isInstanceOf( + org.springframework.boot.testcontainers.properties.TestcontainersPropertySource.DynamicPropertyRegistryInjectionException.class) + .hasMessageStartingWith( + "Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated")); + } + + @Test + @SuppressWarnings("removal") + @Deprecated(since = "3.4.0", forRemoval = true) + void registeringADynamicPropertyCanLogAWarningAndContributeProperty(CapturedOutput output) { + List events = new ArrayList<>(); + this.contextRunner.withPropertyValues("spring.testcontainers.dynamic-property-registry-injection=warn") + .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(BeforeTestcontainersPropertySuppliedEvent.class::isInstance)) + assertThat(events.stream() + .filter(org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent.class::isInstance)) .hasSize(1); + assertThat(output) + .contains("Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated"); }); } + @Test + @SuppressWarnings("removal") + @Deprecated(since = "3.4.0", forRemoval = true) + void registeringADynamicPropertyCanBePermittedAndContributeProperty(CapturedOutput output) { + List events = new ArrayList<>(); + this.contextRunner.withPropertyValues("spring.testcontainers.dynamic-property-registry-injection=allow") + .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(org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent.class::isInstance)) + .hasSize(1); + assertThat(output) + .doesNotContain("Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated"); + }); + } + + @Test + void dynamicPropertyRegistrarBeanContributesProperties(CapturedOutput output) { + this.contextRunner.withUserConfiguration(ContainerAndPropertyRegistrarConfiguration.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) @@ -78,6 +131,23 @@ RedisContainer redisContainer(DynamicPropertyRegistry properties) { } + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(ContainerProperties.class) + @Import(TestBean.class) + static class ContainerAndPropertyRegistrarConfiguration { + + @Bean + RedisContainer redisContainer() { + return TestImage.container(RedisContainer.class); + } + + @Bean + DynamicPropertyRegistrar redisProperties(RedisContainer container) { + return (registry) -> registry.add("container.port", container::getFirstMappedPort); + } + + } + @ConfigurationProperties("container") record ContainerProperties(int port) { } diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.java new file mode 100644 index 000000000000..ea791d179cc5 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.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.testcontainers.properties; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.properties.TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.TestConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.test.context.DynamicPropertyRegistrar; +import org.springframework.test.context.DynamicPropertyRegistry; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TestcontainersPropertySourceAutoConfiguration} when combined with + * {@link SpringBootTest @SpringBootTest}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +@SpringBootTest(classes = TestConfig.class, + properties = "spring.testcontainers.dynamic-property-registry-injection=allow") +class TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest { + + @Autowired + private Environment environment; + + @Test + void injectsRegistryIntoBeanMethod() { + assertThat(this.environment.getProperty("from.bean.method")).isEqualTo("one"); + } + + @Test + void callsRegistrars() { + assertThat(this.environment.getProperty("from.registrar")).isEqualTo("two"); + } + + @TestConfiguration + @ImportAutoConfiguration(TestcontainersPropertySourceAutoConfiguration.class) + @SpringBootConfiguration + static class TestConfig { + + @Bean + String example(DynamicPropertyRegistry registry) { + registry.add("from.bean.method", () -> "one"); + return "Hello"; + } + + @Bean + DynamicPropertyRegistrar propertyRegistrar() { + return (registry) -> registry.add("from.registrar", () -> "two"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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 index 85a958359fb9..d4f6ff944ebb 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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 @@ -18,6 +18,7 @@ import java.util.Set; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -32,7 +33,6 @@ import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; 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; @@ -134,7 +134,7 @@ static class WithRedisAutoConfiguration { static class ContainerConfiguration { @Bean - @ServiceConnection("redis") + @ServiceConnection RedisContainer redisContainer() { return TestImage.container(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 index ff8a8555c5b9..c53c3e3ad988 100644 --- 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 @@ -30,7 +30,7 @@ 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.ActiveMQContainer; +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; @@ -51,7 +51,7 @@ class ActiveMQContainerConnectionDetailsFactoryIntegrationTests { @Container @ServiceConnection - static final ActiveMQContainer activemq = TestImage.container(ActiveMQContainer.class); + static final SymptomaActiveMQContainer activemq = TestImage.container(SymptomaActiveMQContainer.class); @Autowired private JmsMessagingTemplate jmsTemplate; 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/dockerTest/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 index 19535dc537f7..2747d5a29bbf 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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,7 +18,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import org.junit.jupiter.api.Test; -import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.cassandra.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -44,7 +44,7 @@ class CassandraContainerConnectionDetailsFactoryTests { @Container @ServiceConnection - static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); @Autowired(required = false) private CassandraConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..6d13f8edddcc --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactoryTests.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.cassandra; + +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; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.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 DeprecatedCassandraContainerConnectionDetailsFactory}. + * + * @author Andy Wilkinson + * @deprecated since 3.4.0 for removal in 3.6.0 + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@Deprecated(since = "3.4.0", forRemoval = true) +class DeprecatedCassandraContainerConnectionDetailsFactoryTests { + + @Container + @ServiceConnection + static final CassandraContainer cassandra = TestImage.container(CassandraContainer.class); + + @Autowired(required = false) + private CassandraConnectionDetails connectionDetails; + + @Autowired + private CqlSession cqlSession; + + @Test + void connectionCanBeMadeToCassandraContainer() { + assertThat(this.connectionDetails).isNotNull(); + assertThat(this.cqlSession.getMetadata().getNodes()).hasSize(1); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(CassandraAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..a2e4a504401d --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.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.testcontainers.service.connection.hazelcast; + +import java.util.UUID; +import java.util.function.Consumer; + +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.client.impl.clientside.HazelcastClientProxy; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +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.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.HazelcastContainer; +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 HazelcastContainerConnectionDetailsFactory} with a custom hazelcast + * cluster name. + * + * @author Dmytro Nosan + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final HazelcastContainer hazelcast = TestImage.container(HazelcastContainer.class) + .withClusterName("spring-boot"); + + @Autowired(required = false) + private HazelcastConnectionDetails connectionDetails; + + @Autowired + private HazelcastInstance hazelcastInstance; + + @Test + void connectionCanBeMadeToHazelcastContainer() { + assertThat(this.connectionDetails).isNotNull(); + assertThat(this.hazelcastInstance).satisfies(clusterName("spring-boot")); + IMap map = this.hazelcastInstance.getMap(UUID.randomUUID().toString()); + map.put("test", "containers"); + assertThat(map.get("test")).isEqualTo("containers"); + } + + private static Consumer clusterName(String name) { + return (hazelcastInstance) -> { + assertThat(hazelcastInstance).isInstanceOf(HazelcastClientProxy.class); + HazelcastClientProxy proxy = (HazelcastClientProxy) hazelcastInstance; + assertThat(proxy.getClientConfig()).extracting(ClientConfig::getClusterName).isEqualTo(name); + }; + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(HazelcastAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..876b9f1e8bb7 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.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.testcontainers.service.connection.hazelcast; + +import java.util.UUID; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +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.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.HazelcastContainer; +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 HazelcastContainerConnectionDetailsFactory}. + * + * @author Dmytro Nosan + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class HazelcastContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final HazelcastContainer hazelcast = TestImage.container(HazelcastContainer.class); + + @Autowired(required = false) + private HazelcastConnectionDetails connectionDetails; + + @Autowired + private HazelcastInstance hazelcastInstance; + + @Test + void connectionCanBeMadeToHazelcastContainer() { + assertThat(this.connectionDetails).isNotNull(); + IMap map = this.hazelcastInstance.getMap(UUID.randomUUID().toString()); + map.put("test", "containers"); + assertThat(map.get("test")).isEqualTo("containers"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(HazelcastAutoConfiguration.class) + static class TestConfiguration { + + } + +} 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..d759a62a1c37 --- /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 ApacheKafkaContainerConnectionDetailsFactory}. + * + * @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..50db3de4f354 --- /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.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.kafka.ConfluentKafkaContainer; + +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 ConfluentKafkaContainer kafka = TestImage.container(ConfluentKafkaContainer.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/DeprecatedConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/DeprecatedConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..3d9b0aedfe0f --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/DeprecatedConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests.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.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 DeprecatedConfluentKafkaContainerConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +@TestPropertySource(properties = { "spring.kafka.consumer.group-id=test-group", + "spring.kafka.consumer.auto-offset-reset=earliest" }) +@Deprecated(since = "3.4.0", forRemoval = true) +class DeprecatedConfluentKafkaContainerConnectionDetailsFactoryIntegrationTests { + + @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/KafkaContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java deleted file mode 100644 index c26eab677403..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java +++ /dev/null @@ -1,95 +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.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 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 = 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..bc9e201dadd5 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/OpenLdapContainerConnectionDetailsFactoryIntegrationTests.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.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).singleElement().isEqualTo("example"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ LdapAutoConfiguration.class }) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..c5ff6bf76a70 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.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.otlp; + +import org.junit.jupiter.api.Test; +import org.testcontainers.grafana.LgtmStackContainer; +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.logging.otlp.OtlpLoggingAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +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 GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final LgtmStackContainer container = TestImage.container(LgtmStackContainer.class); + + @Autowired + private OtlpLoggingConnectionDetails connectionDetails; + + @Test + void connectionCanBeMadeToOpenTelemetryContainer() { + assertThat(this.connectionDetails.getUrl(Transport.GRPC)) + .isEqualTo("%s/v1/logs".formatted(container.getOtlpGrpcUrl())); + assertThat(this.connectionDetails.getUrl(Transport.HTTP)) + .isEqualTo("%s/v1/logs".formatted(container.getOtlpHttpUrl())); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(OtlpLoggingAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..fed9b1ed0639 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests.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.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.grafana.LgtmStackContainer; +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.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.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@TestPropertySource(properties = { "management.otlp.metrics.export.resource-attributes.service.name=test", + "management.otlp.metrics.export.step=1s" }) +@Testcontainers(disabledWithoutDocker = true) +class GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final LgtmStackContainer container = TestImage.container(LgtmStackContainer.class); + + @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.given() + .pollInterval(Duration.ofSeconds(2)) + .atMost(Duration.ofSeconds(10)) + .ignoreExceptions() + .untilAsserted(() -> { + Response response = RestAssured.given() + .queryParam("query", "{job=\"test\"}") + .get("%s/api/v1/query".formatted(container.getPromehteusHttpUrl())) + .prettyPeek() + .thenReturn(); + assertThat(response.getStatusCode()).isEqualTo(200); + assertThat(response.body() + .jsonPath() + .getList("data.result.find { it.metric.__name__ == 'test_counter_total' }.value")).contains("42"); + assertThat(response.body() + .jsonPath() + .getList("data.result.find { it.metric.__name__ == 'test_gauge' }.value")).contains("12"); + assertThat(response.body() + .jsonPath() + .getList("data.result.find { it.metric.__name__ == 'test_timer_milliseconds_count' }.value")) + .contains("1"); + assertThat(response.body() + .jsonPath() + .getList("data.result.find { it.metric.__name__ == 'test_timer_milliseconds_sum' }.value")) + .contains("123"); + assertThat(response.body() + .jsonPath() + .getList( + "data.result.find { it.metric.__name__ == 'test_timer_milliseconds_bucket' & it.metric.le == '+Inf' }.value")) + .contains("1"); + assertThat(response.body() + .jsonPath() + .getList("data.result.find { it.metric.__name__ == 'test_distributionsummary_count' }.value")) + .contains("1"); + assertThat(response.body() + .jsonPath() + .getList("data.result.find { it.metric.__name__ == 'test_distributionsummary_sum' }.value")) + .contains("24"); + assertThat(response.body() + .jsonPath() + .getList( + "data.result.find { it.metric.__name__ == 'test_distributionsummary_bucket' & it.metric.le == '+Inf' }.value")) + .contains("1"); + }); + } + + @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/GrafanaOpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..9586c0083081 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests.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.otlp; + +import org.junit.jupiter.api.Test; +import org.testcontainers.grafana.LgtmStackContainer; +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.OtlpTracingAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; +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 GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class GrafanaOpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final LgtmStackContainer container = TestImage.container(LgtmStackContainer.class); + + @Autowired + private OtlpTracingConnectionDetails connectionDetails; + + @Test + void connectionCanBeMadeToOpenTelemetryContainer() { + assertThat(this.connectionDetails.getUrl(Transport.HTTP)) + .isEqualTo("%s/v1/traces".formatted(container.getOtlpHttpUrl())); + assertThat(this.connectionDetails.getUrl(Transport.GRPC)) + .isEqualTo("%s/v1/traces".formatted(container.getOtlpGrpcUrl())); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(OtlpTracingAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..6dfab28bab83 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests.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.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.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration; +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 OpenTelemetryLoggingContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class OpenTelemetryLoggingContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final GenericContainer container = TestImage.OPENTELEMETRY.genericContainer() + .withExposedPorts(4317, 4318); + + @Autowired + private OtlpLoggingConnectionDetails connectionDetails; + + @Test + void connectionCanBeMadeToOpenTelemetryContainer() { + assertThat(this.connectionDetails.getUrl(Transport.HTTP)) + .isEqualTo("http://" + container.getHost() + ":" + container.getMappedPort(4318) + "/v1/logs"); + assertThat(this.connectionDetails.getUrl(Transport.GRPC)) + .isEqualTo("http://" + container.getHost() + ":" + container.getMappedPort(4317) + "/v1/logs"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(OtlpTracingAutoConfiguration.class) + static class TestConfiguration { + + } + +} 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 index 6d8760f1faaf..82bd8ccea740 100644 --- 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 @@ -22,8 +22,9 @@ 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.OtlpTracingAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.container.TestImage; @@ -43,19 +44,22 @@ class OpenTelemetryTracingContainerConnectionDetailsFactoryIntegrationTests { @Container @ServiceConnection - static final GenericContainer container = TestImage.OPENTELEMETRY.genericContainer().withExposedPorts(4318); + static final GenericContainer container = TestImage.OPENTELEMETRY.genericContainer() + .withExposedPorts(4317, 4318); @Autowired private OtlpTracingConnectionDetails connectionDetails; @Test void connectionCanBeMadeToOpenTelemetryContainer() { - assertThat(this.connectionDetails.getUrl()) + assertThat(this.connectionDetails.getUrl(Transport.HTTP)) .isEqualTo("http://" + container.getHost() + ":" + container.getMappedPort(4318) + "/v1/traces"); + assertThat(this.connectionDetails.getUrl(Transport.GRPC)) + .isEqualTo("http://" + container.getHost() + ":" + container.getMappedPort(4317) + "/v1/traces"); } @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(OtlpAutoConfiguration.class) + @ImportAutoConfiguration(OtlpTracingAutoConfiguration.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 index 6ebe0b92c2bd..32525783010e 100644 --- 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 @@ -20,7 +20,6 @@ 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; @@ -63,7 +62,7 @@ class PulsarContainerConnectionDetailsFactoryIntegrationTests { private TestListener listener; @Test - void connectionCanBeMadeToPulsarContainer() throws PulsarClientException { + void connectionCanBeMadeToPulsarContainer() { this.pulsarTemplate.send("test-topic", "test-data"); Awaitility.waitAtMost(Duration.ofSeconds(30)) .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("test-data")); diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..5d8dc04158fe --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.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.service.connection.redis; + +import java.util.Map; + +import com.redis.testcontainers.RedisContainer; +import com.redis.testcontainers.RedisStackContainer; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testcontainers.service.connection.TestContainerConnectionSource; +import org.springframework.core.annotation.MergedAnnotation; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link RedisContainerConnectionDetailsFactory} when using a custom container + * without "redis" as the name. + * + * @author Phillip Webb + */ +class CustomRedisContainerConnectionDetailsFactoryTests { + + @Test + void getConnectionDetailsWhenRedisContainerWithCustomName() { + ConnectionDetailsFactories factories = new ConnectionDetailsFactories(); + MergedAnnotation annotation = MergedAnnotation.of(ServiceConnection.class, + Map.of("value", "")); + ContainerConnectionSource source = TestContainerConnectionSource.create("test", null, + RedisContainer.class, "mycustomimage", annotation, null); + Map, ConnectionDetails> connectionDetails = factories.getConnectionDetails(source, true); + assertThat(connectionDetails.get(RedisConnectionDetails.class)).isNotNull(); + } + + @Test + void getConnectionDetailsWhenRedisStackContainerWithCustomName() { + ConnectionDetailsFactories factories = new ConnectionDetailsFactories(); + MergedAnnotation annotation = MergedAnnotation.of(ServiceConnection.class, + Map.of("value", "")); + ContainerConnectionSource source = TestContainerConnectionSource.create("test", null, + RedisStackContainer.class, "mycustomimage", annotation, null); + Map, ConnectionDetails> connectionDetails = factories.getConnectionDetails(source, true); + assertThat(connectionDetails.get(RedisConnectionDetails.class)).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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 index 29dfbbfb84fe..e7c68c4140b3 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/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 @@ -16,6 +16,7 @@ package org.springframework.boot.testcontainers.service.connection.redis; +import com.redis.testcontainers.RedisContainer; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -25,7 +26,6 @@ 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.RedisContainer; import org.springframework.boot.testsupport.container.TestImage; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; 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..1616e8f1eb84 --- /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 com.redis.testcontainers.RedisStackContainer; +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.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/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 95a8fce48ff3..7aa373300753 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 +19,15 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.testcontainers.containers.Container; +import org.testcontainers.lifecycle.Startable; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.autoconfigure.container.ContainerImageMetadata; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -34,12 +38,17 @@ */ class ContainerFieldsImporter { - void registerBeanDefinitions(BeanDefinitionRegistry registry, Class definitionClass) { + Set registerBeanDefinitions(BeanDefinitionRegistry registry, Class definitionClass) { + Set importedContainers = new HashSet<>(); for (Field field : getContainerFields(definitionClass)) { assertValid(field); Container container = getContainer(field); + if (container instanceof Startable startable) { + importedContainers.add(startable); + } registerBeanDefinition(registry, field, container); } + return importedContainers; } private List getContainerFields(Class containersClass) { @@ -65,7 +74,9 @@ private Container getContainer(Field field) { } private void registerBeanDefinition(BeanDefinitionRegistry registry, Field field, Container container) { + ContainerImageMetadata containerMetadata = new ContainerImageMetadata(container.getDockerImageName()); TestcontainerFieldBeanDefinition beanDefinition = new TestcontainerFieldBeanDefinition(field, container); + containerMetadata.addTo(beanDefinition); String beanName = generateBeanName(field); registry.registerBeanDefinition(beanName, beanDefinition); } 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 d680f7504c81..be7cc9e11247 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 @@ -19,12 +19,16 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Set; +import java.util.function.Supplier; +import org.testcontainers.lifecycle.Startable; + +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.env.Environment; +import org.springframework.test.context.DynamicPropertyRegistrar; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.util.Assert; @@ -32,30 +36,29 @@ /** * Used by {@link ImportTestcontainersRegistrar} to import - * {@link DynamicPropertySource @DynamicPropertySource} methods. + * {@link DynamicPropertySource @DynamicPropertySource} through a + * {@link DynamicPropertyRegistrar}. * * @author Phillip Webb + * @author Andy Wilkinson */ class DynamicPropertySourceMethodsImporter { - private final Environment environment; - - DynamicPropertySourceMethodsImporter(Environment environment) { - this.environment = environment; - } - - void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistry, Class definitionClass) { + void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistry, Class definitionClass, + Set importedContainers) { Set methods = MethodIntrospector.selectMethods(definitionClass, this::isAnnotated); if (methods.isEmpty()) { return; } - DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource.attach(this.environment, - beanDefinitionRegistry); - methods.forEach((method) -> { - assertValid(method); - ReflectionUtils.makeAccessible(method); - ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry); - }); + methods.forEach((method) -> assertValid(method)); + RootBeanDefinition registrarDefinition = new RootBeanDefinition(); + registrarDefinition.setBeanClass(DynamicPropertySourcePropertyRegistrar.class); + ConstructorArgumentValues arguments = new ConstructorArgumentValues(); + arguments.addGenericArgumentValue(methods); + arguments.addGenericArgumentValue(importedContainers); + registrarDefinition.setConstructorArgumentValues(arguments); + beanDefinitionRegistry.registerBeanDefinition(definitionClass.getName() + ".dynamicPropertyRegistrar", + registrarDefinition); } private boolean isAnnotated(Method method) { @@ -71,4 +74,52 @@ private void assertValid(Method method) { + "' must accept a single DynamicPropertyRegistry argument"); } + static class DynamicPropertySourcePropertyRegistrar implements DynamicPropertyRegistrar { + + private final Set methods; + + private final Set containers; + + DynamicPropertySourcePropertyRegistrar(Set methods, Set containers) { + this.methods = methods; + this.containers = containers; + } + + @Override + public void accept(DynamicPropertyRegistry registry) { + DynamicPropertyRegistry containersBackedRegistry = new ContainersBackedDynamicPropertyRegistry(registry, + this.containers); + this.methods.forEach((method) -> { + ReflectionUtils.makeAccessible(method); + ReflectionUtils.invokeMethod(method, null, containersBackedRegistry); + }); + } + + } + + static class ContainersBackedDynamicPropertyRegistry implements DynamicPropertyRegistry { + + private final DynamicPropertyRegistry delegate; + + private final Set containers; + + ContainersBackedDynamicPropertyRegistry(DynamicPropertyRegistry delegate, Set containers) { + this.delegate = delegate; + this.containers = containers; + } + + @Override + public void add(String name, Supplier valueSupplier) { + this.delegate.add(name, () -> { + startContainers(); + return valueSupplier.get(); + }); + } + + private void startContainers() { + this.containers.forEach(Startable::start); + } + + } + } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainers.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainers.java index 5f99743017ba..f20b98651f35 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainers.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.testcontainers.containers.Container; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.properties.TestcontainersPropertySourceAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; @@ -43,6 +45,7 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ImportTestcontainersRegistrar.class) +@ImportAutoConfiguration(TestcontainersPropertySourceAutoConfiguration.class) public @interface ImportTestcontainers { /** 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 1c2cf49725c3..e84a9e745370 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 @@ -16,6 +16,10 @@ package org.springframework.boot.testcontainers.context; +import java.util.Set; + +import org.testcontainers.lifecycle.Startable; + import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.MergedAnnotation; @@ -43,7 +47,7 @@ class ImportTestcontainersRegistrar implements ImportBeanDefinitionRegistrar { ImportTestcontainersRegistrar(Environment environment) { this.containerFieldsImporter = new ContainerFieldsImporter(); this.dynamicPropertySourceMethodsImporter = (!ClassUtils.isPresent(DYNAMIC_PROPERTY_SOURCE_CLASS, null)) ? null - : new DynamicPropertySourceMethodsImporter(environment); + : new DynamicPropertySourceMethodsImporter(); } @Override @@ -60,9 +64,11 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B private void registerBeanDefinitions(BeanDefinitionRegistry registry, Class[] definitionClasses) { for (Class definitionClass : definitionClasses) { - this.containerFieldsImporter.registerBeanDefinitions(registry, definitionClass); + Set importedContainers = this.containerFieldsImporter.registerBeanDefinitions(registry, + definitionClass); if (this.dynamicPropertySourceMethodsImporter != null) { - this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(registry, definitionClass); + this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(registry, definitionClass, + importedContainers); } } } 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 index c31963302fc1..eeab21286084 100644 --- 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 @@ -19,13 +19,18 @@ import org.testcontainers.containers.Container; import org.springframework.context.ApplicationEvent; +import org.springframework.test.context.DynamicPropertyRegistrar; /** * Event published just before a Testcontainers {@link Container} is used. * * @author Andy Wilkinson * @since 3.2.6 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of property registration using a + * {@link DynamicPropertyRegistrar} bean that injects the {@link Container} from which the + * properties will be sourced. */ +@Deprecated(since = "3.4.0", forRemoval = true) public class BeforeTestcontainerUsedEvent extends ApplicationEvent { public BeforeTestcontainerUsedEvent(Object source) { 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 26a8cc9187fd..f1754048e98a 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 @@ -40,6 +40,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.context.ApplicationListener; +import org.springframework.context.aot.AbstractAotProcessor; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.log.LogMessage; @@ -58,6 +59,7 @@ * @author Scott Frederick * @see TestcontainersLifecycleApplicationContextInitializer */ +@SuppressWarnings({ "removal", "deprecation" }) @Order(Ordered.LOWEST_PRECEDENCE) class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor, ApplicationListener { @@ -79,6 +81,7 @@ class TestcontainersLifecycleBeanPostProcessor } @Override + @Deprecated(since = "3.4.0", forRemoval = true) public void onApplicationEvent(BeforeTestcontainerUsedEvent event) { initializeContainers(); } @@ -101,7 +104,7 @@ else if (this.startables.get() == Startables.STARTED) { } private boolean isAotProcessingInProgress() { - return Boolean.getBoolean("spring.aot.processing"); + return Boolean.getBoolean(AbstractAotProcessor.AOT_PROCESSING); } private void initializeStartables(Startable startableBean, String startableBeanName) { diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java deleted file mode 100644 index 0ed3c395936d..000000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java +++ /dev/null @@ -1,50 +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.testcontainers.properties; - -import java.util.function.Supplier; - -import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; - -/** - * Event published just before the {@link Supplier value supplier} of a - * {@link TestcontainersPropertySource} property is called. - * - * @author Phillip Webb - * @since 3.2.2 - * @deprecated since 3.2.6 for removal in 3.4.0 in favor of - * {@link BeforeTestcontainerUsedEvent} - */ -@Deprecated(since = "3.2.6", forRemoval = true) -public class BeforeTestcontainersPropertySuppliedEvent extends BeforeTestcontainerUsedEvent { - - private final String propertyName; - - BeforeTestcontainersPropertySuppliedEvent(TestcontainersPropertySource source, String propertyName) { - super(source); - this.propertyName = propertyName; - } - - /** - * Return the name of the property about to be supplied. - * @return the propertyName the property name - */ - public String getPropertyName() { - return this.propertyName; - } - -} 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 f1ecfe878c80..f9a5730242d6 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 @@ -23,6 +23,8 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Supplier; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.testcontainers.containers.Container; import org.springframework.beans.BeansException; @@ -30,16 +32,21 @@ 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.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Binder; +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.DynamicPropertyRegistrar; 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 @@ -47,8 +54,14 @@ * * @author Phillip Webb * @since 3.1.0 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of declaring one or more + * {@link DynamicPropertyRegistrar} beans. */ -public class TestcontainersPropertySource extends EnumerablePropertySource>> { +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) +public class TestcontainersPropertySource extends MapPropertySource { + + private static final Log logger = LogFactory.getLog(TestcontainersPropertySource.class); static final String NAME = "testcontainersPropertySource"; @@ -56,14 +69,16 @@ public class TestcontainersPropertySource extends EnumerablePropertySource eventPublishers = new CopyOnWriteArraySet<>(); - TestcontainersPropertySource() { - this(Collections.synchronizedMap(new LinkedHashMap<>())); + TestcontainersPropertySource(DynamicPropertyRegistryInjection registryInjection) { + this(Collections.synchronizedMap(new LinkedHashMap<>()), registryInjection); } - private TestcontainersPropertySource(Map> valueSuppliers) { + private TestcontainersPropertySource(Map> valueSuppliers, + DynamicPropertyRegistryInjection registryInjection) { super(NAME, Collections.unmodifiableMap(valueSuppliers)); this.registry = (name, valueSupplier) -> { Assert.hasText(name, "'name' must not be null or blank"); + DynamicPropertyRegistryInjectionException.throwIfNecessary(name, registryInjection); Assert.notNull(valueSupplier, "'valueSupplier' must not be null"); valueSuppliers.put(name, valueSupplier); }; @@ -75,25 +90,14 @@ private void addEventPublisher(ApplicationEventPublisher eventPublisher) { @Override public Object getProperty(String name) { - Supplier valueSupplier = this.source.get(name); + Object valueSupplier = this.source.get(name); return (valueSupplier != null) ? getProperty(name, valueSupplier) : null; } - @SuppressWarnings({ "removal", "deprecation" }) - private Object getProperty(String name, Supplier valueSupplier) { - BeforeTestcontainersPropertySuppliedEvent event = new BeforeTestcontainersPropertySuppliedEvent(this, name); + private Object getProperty(String name, Object valueSupplier) { + BeforeTestcontainerUsedEvent event = new BeforeTestcontainerUsedEvent(this); this.eventPublishers.forEach((eventPublisher) -> eventPublisher.publishEvent(event)); - return valueSupplier.get(); - } - - @Override - public boolean containsProperty(String name) { - return this.source.containsKey(name); - } - - @Override - public String[] getPropertyNames() { - return StringUtils.toStringArray(this.source.keySet()); + return SupplierUtils.resolve(valueSupplier); } public static DynamicPropertyRegistry attach(Environment environment) { @@ -126,7 +130,12 @@ else if (registry != null && !registry.containsBeanDefinition(EventPublisherRegi static TestcontainersPropertySource getOrAdd(ConfigurableEnvironment environment) { PropertySource propertySource = environment.getPropertySources().get(NAME); if (propertySource == null) { - environment.getPropertySources().addFirst(new TestcontainersPropertySource()); + BindResult bindingResult = Binder.get(environment) + .bind("spring.testcontainers.dynamic-property-registry-injection", + DynamicPropertyRegistryInjection.class); + environment.getPropertySources() + .addFirst( + new TestcontainersPropertySource(bindingResult.orElse(DynamicPropertyRegistryInjection.FAIL))); return getOrAdd(environment); } Assert.state(propertySource instanceof TestcontainersPropertySource, @@ -166,4 +175,34 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) } + private enum DynamicPropertyRegistryInjection { + + ALLOW, + + FAIL, + + WARN + + } + + static final class DynamicPropertyRegistryInjectionException extends RuntimeException { + + private DynamicPropertyRegistryInjectionException(String propertyName) { + super("Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated. Register '" + + propertyName + "' using a DynamicPropertyRegistrar bean instead. Alternatively, set " + + "spring.testcontainers.dynamic-property-registry-injection to 'warn' to replace this " + + "failure with a warning or to 'allow' to permit injection of the registry."); + } + + private static void throwIfNecessary(String propertyName, DynamicPropertyRegistryInjection registryInjection) { + switch (registryInjection) { + case FAIL -> throw new DynamicPropertyRegistryInjectionException(propertyName); + case WARN -> logger + .warn("Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated. Register '" + + propertyName + "' using a DynamicPropertyRegistrar bean instead."); + } + } + + } + } 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 e6219f2d2d4e..0d6271906a16 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 @@ -16,19 +16,27 @@ package org.springframework.boot.testcontainers.properties; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Role; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.support.DynamicPropertyRegistrarBeanInitializer; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration - * Auto-configuration} to add {@link TestcontainersPropertySource} support. + * Auto-configuration} to add support for properties sourced from a Testcontainers + * {@link GenericContainer container}. * * @author Phillip Webb + * @author Andy Wilkinson * @since 3.1.0 */ @AutoConfiguration @@ -36,12 +44,18 @@ @ConditionalOnClass(DynamicPropertyRegistry.class) public class TestcontainersPropertySourceAutoConfiguration { - TestcontainersPropertySourceAutoConfiguration() { - } - @Bean + @SuppressWarnings("removal") + @Deprecated(since = "3.4.0", forRemoval = true) static DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableApplicationContext applicationContext) { return TestcontainersPropertySource.attach(applicationContext); } + @Bean + @ConditionalOnMissingBean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + static DynamicPropertyRegistrarBeanInitializer dynamicPropertyRegistrarBeanInitializer() { + return new DynamicPropertyRegistrarBeanInitializer(); + } + } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java index 318ef6a5f91d..491d61c5d6af 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.container.ContainerImageMetadata; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactoryNotFoundException; @@ -100,12 +101,14 @@ private void registerBeanDefinition(BeanDefinitionRegistry registry, Contain Arrays.asList(existingBeans)))); return; } + ContainerImageMetadata containerMetadata = new ContainerImageMetadata(source.getContainerImageName()); String beanName = getBeanName(source, connectionDetails); Class beanType = (Class) connectionDetails.getClass(); Supplier beanSupplier = () -> (T) connectionDetails; logger.debug(LogMessage.of(() -> "Registering '%s' for %s".formatted(beanName, source))); RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType, beanSupplier); beanDefinition.setAttribute(ServiceConnection.class.getName(), true); + containerMetadata.addTo(beanDefinition); registry.registerBeanDefinition(beanName, beanDefinition); } 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 b4bb38e17e70..af6e6f916e7b 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,11 +17,13 @@ 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; import org.apache.commons.logging.LogFactory; import org.testcontainers.containers.Container; +import org.testcontainers.lifecycle.Startable; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -31,10 +33,8 @@ 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; @@ -61,7 +61,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; @@ -80,7 +80,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; } @@ -91,9 +103,9 @@ public final D getConnectionDetails(ContainerConnectionSource source) { } try { Class[] generics = resolveGenerics(); - Class containerType = generics[0]; - Class connectionDetailsType = generics[1]; - if (source.accepts(this.connectionName, containerType, connectionDetailsType)) { + Class requiredContainerType = generics[0]; + Class requiredConnectionDetailsType = generics[1]; + if (sourceAccepts(source, requiredContainerType, requiredConnectionDetailsType)) { return getContainerConnectionDetails(source); } } @@ -103,6 +115,25 @@ public final D getConnectionDetails(ContainerConnectionSource source) { return null; } + /** + * Return if the given source accepts the connection. By default this method checks + * each connection name. + * @param source the container connection source + * @param requiredContainerType the required container type + * @param requiredConnectionDetailsType the required connection details type + * @return if the source accepts the connection + * @since 3.4.0 + */ + protected boolean sourceAccepts(ContainerConnectionSource source, Class requiredContainerType, + Class requiredConnectionDetailsType) { + for (String requiredConnectionName : this.connectionNames) { + if (source.accepts(requiredConnectionName, requiredContainerType, requiredConnectionDetailsType)) { + return true; + } + } + return false; + } + private boolean hasRequiredClasses() { return ObjectUtils.isEmpty(this.requiredClassNames) || Arrays.stream(this.requiredClassNames) .allMatch((requiredClassName) -> ClassUtils.isPresent(requiredClassName, null)); @@ -132,8 +163,6 @@ protected static class ContainerConnectionDetails> private final ContainerConnectionSource source; - private volatile ApplicationEventPublisher eventPublisher; - private volatile C container; /** @@ -158,7 +187,9 @@ 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)); + if (this.container instanceof Startable startable) { + startable.start(); + } return this.container; } @@ -168,8 +199,8 @@ public Origin getOrigin() { } @Override + @Deprecated(since = "3.4.0", forRemoval = true) public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.eventPublisher = applicationContext; } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java index 30aece533d18..d589b4274dd6 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 @@ public final class ContainerConnectionSource> implements private final Class containerType; + private final String containerImageName; + private final String connectionName; private final Set> connectionDetailsTypes; @@ -63,6 +65,7 @@ public final class ContainerConnectionSource> implements this.beanNameSuffix = beanNameSuffix; this.origin = origin; this.containerType = containerType; + this.containerImageName = containerImageName; this.connectionName = getOrDeduceConnectionName(annotation.getString("name"), containerImageName); this.connectionDetailsTypes = Set.of(annotation.getClassArray("type")); this.containerSupplier = containerSupplier; @@ -73,6 +76,7 @@ public final class ContainerConnectionSource> implements this.beanNameSuffix = beanNameSuffix; this.origin = origin; this.containerType = containerType; + this.containerImageName = containerImageName; this.connectionName = getOrDeduceConnectionName(annotation.name(), containerImageName); this.connectionDetailsTypes = Set.of(annotation.type()); this.containerSupplier = containerSupplier; @@ -90,7 +94,15 @@ private static String getOrDeduceConnectionName(String connectionName, String co return null; } - boolean accepts(String requiredConnectionName, Class requiredContainerType, + /** + * Return if this source accepts the given connection. + * @param requiredConnectionName the required connection name or {@code null} + * @param requiredContainerType the required container type + * @param requiredConnectionDetailsType the required connection details type + * @return if the connection is accepted by this source + * @since 3.4.0 + */ + public boolean accepts(String requiredConnectionName, Class requiredContainerType, Class requiredConnectionDetailsType) { if (StringUtils.hasText(requiredConnectionName) && !requiredConnectionName.equalsIgnoreCase(this.connectionName)) { @@ -128,6 +140,10 @@ public Origin getOrigin() { return this.origin; } + String getContainerImageName() { + return this.containerImageName; + } + String getConnectionName() { return this.connectionName; } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java index 1f18f06c1a0d..be70abf4527c 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.testcontainers.containers.Container; import org.springframework.boot.origin.Origin; +import org.springframework.context.aot.AbstractAotProcessor; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.test.context.ContextConfigurationAttributes; @@ -96,7 +97,7 @@ private Object getFieldValue(Field field) { } private boolean isAotProcessingInProgress() { - return Boolean.getBoolean("spring.aot.processing"); + return Boolean.getBoolean(AbstractAotProcessor.AOT_PROCESSING); } } 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/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/cassandra/CassandraContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java index c2dc8fc6ff99..e8b82096ff07 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,7 @@ import java.net.InetSocketAddress; import java.util.List; -import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.cassandra.CassandraContainer; import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; @@ -36,11 +36,11 @@ * @author Phillip Webb */ class CassandraContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory, CassandraConnectionDetails> { + extends ContainerConnectionDetailsFactory { @Override protected CassandraConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource source) { return new CassandraContainerConnectionDetails(source); } @@ -48,9 +48,9 @@ protected CassandraConnectionDetails getContainerConnectionDetails( * {@link CassandraConnectionDetails} backed by a {@link ContainerConnectionSource}. */ private static final class CassandraContainerConnectionDetails - extends ContainerConnectionDetails> implements CassandraConnectionDetails { + extends ContainerConnectionDetails implements CassandraConnectionDetails { - private CassandraContainerConnectionDetails(ContainerConnectionSource> source) { + private CassandraContainerConnectionDetails(ContainerConnectionSource source) { super(source); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..2ed24ac4444c --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactory.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.testcontainers.service.connection.cassandra; + +import java.net.InetSocketAddress; +import java.util.List; + +import org.testcontainers.containers.CassandraContainer; + +import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; +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 CassandraConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link CassandraContainer}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link CassandraContainerConnectionDetailsFactory}. + */ +@Deprecated(since = "3.4.0", forRemoval = true) +class DeprecatedCassandraContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, CassandraConnectionDetails> { + + @Override + protected CassandraConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource> source) { + return new CassandraContainerConnectionDetails(source); + } + + /** + * {@link CassandraConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class CassandraContainerConnectionDetails + extends ContainerConnectionDetails> implements CassandraConnectionDetails { + + private CassandraContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public List getContactPoints() { + InetSocketAddress contactPoint = getContainer().getContactPoint(); + return List.of(new Node(contactPoint.getHostString(), contactPoint.getPort())); + } + + @Override + public String getUsername() { + return getContainer().getUsername(); + } + + @Override + public String getPassword() { + return getContainer().getPassword(); + } + + @Override + public String getLocalDatacenter() { + return getContainer().getLocalDatacenter(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..5203854b13b3 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.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.testcontainers.service.connection.hazelcast; + +import java.util.Map; + +import com.hazelcast.client.config.ClientConfig; +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +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 HazelcastConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} + * using the {@code "hazelcast/hazelcast"} image. + * + * @author Dmytro Nosan + */ +class HazelcastContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, HazelcastConnectionDetails> { + + private static final int DEFAULT_PORT = 5701; + + private static final String CLUSTER_NAME_ENV = "HZ_CLUSTERNAME"; + + HazelcastContainerConnectionDetailsFactory() { + super("hazelcast/hazelcast", "com.hazelcast.client.config.ClientConfig"); + } + + @Override + protected HazelcastConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + return new HazelcastContainerConnectionDetails(source); + } + + /** + * {@link HazelcastConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class HazelcastContainerConnectionDetails extends ContainerConnectionDetails> + implements HazelcastConnectionDetails { + + private HazelcastContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public ClientConfig getClientConfig() { + ClientConfig config = new ClientConfig(); + Container container = getContainer(); + Map env = container.getEnvMap(); + String clusterName = env.get(CLUSTER_NAME_ENV); + if (clusterName != null) { + config.setClusterName(clusterName); + } + config.getNetworkConfig().addAddress(container.getHost() + ":" + container.getMappedPort(DEFAULT_PORT)); + return config; + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java new file mode 100644 index 000000000000..f5113a8902d4 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/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 Hazelcast service connections. + */ +package org.springframework.boot.testcontainers.service.connection.hazelcast; 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..d7a23db29572 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/ConfluentKafkaContainerConnectionDetailsFactory.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.kafka; + +import java.util.List; + +import org.testcontainers.kafka.ConfluentKafkaContainer; + +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 ConfluentKafkaContainer}. + * + * @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/DeprecatedConfluentKafkaContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/DeprecatedConfluentKafkaContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..eafd40c87efa --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/DeprecatedConfluentKafkaContainerConnectionDetailsFactory.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.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 + * @deprecated since 3.4.0 for removal in 3.6.0 in favor of + * {@link ConfluentKafkaContainerConnectionDetailsFactory}. + */ +@Deprecated(since = "3.4.0", forRemoval = true) +class DeprecatedConfluentKafkaContainerConnectionDetailsFactory + 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/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..948c834e6570 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory.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.testcontainers.service.connection.otlp; + +import org.testcontainers.grafana.LgtmStackContainer; + +import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +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 OtlpLoggingConnectionDetails} from a + * {@link ServiceConnection @ServiceConnection}-annotated {@link LgtmStackContainer} using + * the {@code "grafana/otel-lgtm"} image. + * + * @author Eddú Meléndez + */ +class GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, + "org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); + } + + @Override + protected OtlpLoggingConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new OpenTelemetryLoggingContainerConnectionDetails(source); + } + + private static final class OpenTelemetryLoggingContainerConnectionDetails + extends ContainerConnectionDetails implements OtlpLoggingConnectionDetails { + + private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getUrl(Transport transport) { + String url = switch (transport) { + case HTTP -> getContainer().getOtlpHttpUrl(); + case GRPC -> getContainer().getOtlpGrpcUrl(); + }; + return "%s/v1/logs".formatted(url); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..6a51fe9ba8f4 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory.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.otlp; + +import org.testcontainers.grafana.LgtmStackContainer; + +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 LgtmStackContainer} using + * the {@code "grafana/otel-lgtm"} image. + * + * @author Eddú Meléndez + */ +class GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, + "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 "%s/v1/metrics".formatted(getContainer().getOtlpHttpUrl()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..c5aca5ff458e --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory.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.testcontainers.service.connection.otlp; + +import org.testcontainers.grafana.LgtmStackContainer; + +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; +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 LgtmStackContainer} using + * the {@code "grafana/otel-lgtm"} image. + * + * @author Eddú Meléndez + */ +class GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, + "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration"); + } + + @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(Transport transport) { + String url = switch (transport) { + case HTTP -> getContainer().getOtlpHttpUrl(); + case GRPC -> getContainer().getOtlpGrpcUrl(); + }; + return "%s/v1/traces".formatted(url); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..d3eb2fb927fd --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/otlp/OpenTelemetryLoggingContainerConnectionDetailsFactory.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.service.connection.otlp; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.logging.otlp.Transport; +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 OtlpLoggingConnectionDetails} from a + * {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using + * the {@code "otel/opentelemetry-collector-contrib"} image. + * + * @author Eddú Meléndez + * @author Moritz Halbritter + */ +class OpenTelemetryLoggingContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, OtlpLoggingConnectionDetails> { + + private static final int OTLP_GRPC_PORT = 4317; + + private static final int OTLP_HTTP_PORT = 4318; + + OpenTelemetryLoggingContainerConnectionDetailsFactory() { + super("otel/opentelemetry-collector-contrib", + "org.springframework.boot.actuate.autoconfigure.logging.otlp.OtlpLoggingAutoConfiguration"); + } + + @Override + protected OtlpLoggingConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource> source) { + return new OpenTelemetryLoggingContainerConnectionDetails(source); + } + + private static final class OpenTelemetryLoggingContainerConnectionDetails + extends ContainerConnectionDetails> implements OtlpLoggingConnectionDetails { + + private OpenTelemetryLoggingContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public String getUrl(Transport transport) { + int port = switch (transport) { + case HTTP -> OTLP_HTTP_PORT; + case GRPC -> OTLP_GRPC_PORT; + }; + return "http://%s:%d/v1/logs".formatted(getContainer().getHost(), getContainer().getMappedPort(port)); + } + + } + +} 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 index 6c3e72ac797c..6bdb82f59fc3 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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 org.testcontainers.containers.GenericContainer; import org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingConnectionDetails; +import org.springframework.boot.actuate.autoconfigure.tracing.otlp.Transport; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; @@ -35,9 +36,13 @@ class OpenTelemetryTracingContainerConnectionDetailsFactory extends ContainerConnectionDetailsFactory, OtlpTracingConnectionDetails> { + private static final int OTLP_GRPC_PORT = 4317; + + private static final int OTLP_HTTP_PORT = 4318; + OpenTelemetryTracingContainerConnectionDetailsFactory() { super("otel/opentelemetry-collector-contrib", - "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration"); + "org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpTracingAutoConfiguration"); } @Override @@ -54,8 +59,12 @@ private OpenTelemetryTracingContainerConnectionDetails(ContainerConnectionSource } @Override - public String getUrl() { - return "http://%s:%d/v1/traces".formatted(getContainer().getHost(), getContainer().getMappedPort(4318)); + public String getUrl(Transport transport) { + int port = switch (transport) { + case HTTP -> OTLP_HTTP_PORT; + case GRPC -> OTLP_GRPC_PORT; + }; + return "http://%s:%d/v1/traces".formatted(getContainer().getHost(), getContainer().getMappedPort(port)); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/ClickHouseR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/ClickHouseR2dbcContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..4e1177e5bca6 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/ClickHouseR2dbcContainerConnectionDetailsFactory.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.testcontainers.service.connection.r2dbc; + +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.testcontainers.clickhouse.ClickHouseContainer; +import org.testcontainers.clickhouse.ClickHouseR2DBCDatabaseContainer; + +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 ClickHouseContainer}. + * + * @author Eddú Meléndez + */ +class ClickHouseR2dbcContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + ClickHouseR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } + + @Override + public R2dbcConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new ClickHouseR2dbcDatabaseContainerConnectionDetails(source); + } + + /** + * {@link R2dbcConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class ClickHouseR2dbcDatabaseContainerConnectionDetails + extends ContainerConnectionDetails implements R2dbcConnectionDetails { + + private ClickHouseR2dbcDatabaseContainerConnectionDetails( + ContainerConnectionSource source) { + super(source); + } + + @Override + public ConnectionFactoryOptions getConnectionFactoryOptions() { + return ClickHouseR2DBCDatabaseContainer.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..134824058dc2 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,10 @@ package org.springframework.boot.testcontainers.service.connection.redis; +import java.util.List; + +import com.redis.testcontainers.RedisContainer; +import com.redis.testcontainers.RedisStackContainer; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; @@ -32,16 +36,30 @@ * @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 + protected boolean sourceAccepts(ContainerConnectionSource> source, Class requiredContainerType, + Class requiredConnectionDetailsType) { + return super.sourceAccepts(source, requiredContainerType, requiredConnectionDetailsType) + || source.accepts(ANY_CONNECTION_NAME, RedisContainer.class, requiredConnectionDetailsType) + || source.accepts(ANY_CONNECTION_NAME, RedisStackContainer.class, requiredConnectionDetailsType); } @Override - public RedisConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + protected RedisConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { return new RedisContainerConnectionDetails(source); } @@ -57,7 +75,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 index ca82e22875ba..cc24a21765ea 100644 --- 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 @@ -3,8 +3,32 @@ { "name": "spring.testcontainers.beans.startup", "type": "org.springframework.boot.testcontainers.lifecycle.TestcontainersStartup", - "description": "Testcontainers startup modes.", - "defaultValue": "sequential" + "description": "Testcontainers startup modes.", + "defaultValue": "sequential" + }, + { + "name": "spring.testcontainers.dynamic-property-registry-injection", + "description": "How to treat injection of DynamicPropertyRegistry into a @Bean method.", + "defaultValue": "fail" + } + ], + "hints": [ + { + "name": "spring.testcontainers.dynamic-property-registry-injection", + "values": [ + { + "value": "fail", + "description": "Fail with an exception." + }, + { + "value": "warn", + "description": "Log a warning." + }, + { + "value": "allow", + "description": "Allow the use despite its deprecation." + } + ] } ] } 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 386f2a1a3553..902b2c43aca2 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,20 +8,32 @@ 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.cassandra.DeprecatedCassandraContainerConnectionDetailsFactory,\ 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.flyway.FlywayContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.hazelcast.HazelcastContainerConnectionDetailsFactory,\ 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.kafka.DeprecatedConfluentKafkaContainerConnectionDetailsFactory,\ +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.GrafanaOpenTelemetryLoggingContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.otlp.GrafanaOpenTelemetryMetricsContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.otlp.GrafanaOpenTelemetryTracingContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.otlp.OpenTelemetryLoggingContainerConnectionDetailsFactory,\ 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.ClickHouseR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.MariaDbR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.MySqlR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.OracleFreeR2dbcContainerConnectionDetailsFactory,\ 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 9d9da72efc6f..9aea10896e14 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 @@ -30,6 +30,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.aot.AbstractAotProcessor; import org.springframework.core.env.MapPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -149,7 +150,7 @@ void doesNotStartContainersWhenAotProcessingIsInProgress() { GenericContainer container = mock(GenericContainer.class); AnnotationConfigApplicationContext applicationContext = createApplicationContext(container); then(container).shouldHaveNoInteractions(); - withSystemProperty("spring.aot.processing", "true", + withSystemProperty(AbstractAotProcessor.AOT_PROCESSING, "true", () -> applicationContext.refreshForAotProcessing(new RuntimeHints())); then(container).shouldHaveNoInteractions(); applicationContext.close(); 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 8b913d4945b7..177322aeedec 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 @@ -24,10 +24,12 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource.EventPublisherRegistrar; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; import org.springframework.context.ApplicationEvent; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.DynamicPropertyRegistry; @@ -39,10 +41,14 @@ * Tests for {@link TestcontainersPropertySource}. * * @author Phillip Webb + * @deprecated since 3.4.0 for removal in 3.6.0 */ +@SuppressWarnings("removal") +@Deprecated(since = "3.4.0", forRemoval = true) class TestcontainersPropertySourceTests { - private MockEnvironment environment = new MockEnvironment(); + private MockEnvironment environment = new MockEnvironment() + .withProperty("spring.testcontainers.dynamic-property-registry-injection", "allow"); private GenericApplicationContext context = new GenericApplicationContext(); @@ -120,7 +126,8 @@ 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)); + assertThat(this.context.containsBean( + org.springframework.boot.testcontainers.properties.TestcontainersPropertySource.EventPublisherRegistrar.NAME)); } @Test @@ -134,19 +141,22 @@ void attachToEnvironmentAndContextWhenAlreadyAttachedReturnsExisting() { } @Test - @SuppressWarnings("removal") void getPropertyPublishesEvent() { try (GenericApplicationContext applicationContext = new GenericApplicationContext()) { + ConfigurableEnvironment environment = applicationContext.getEnvironment(); + environment.getPropertySources() + .addLast(new MapPropertySource("test", + Map.of("spring.testcontainers.dynamic-property-registry-injection", "allow"))); List events = new ArrayList<>(); applicationContext.addApplicationListener(events::add); - DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(applicationContext.getEnvironment(), + DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(environment, (BeanDefinitionRegistry) applicationContext.getBeanFactory()); applicationContext.refresh(); registry.add("test", () -> "spring"); - assertThat(applicationContext.getEnvironment().containsProperty("test")).isTrue(); + assertThat(environment.containsProperty("test")).isTrue(); assertThat(events.isEmpty()); - assertThat(applicationContext.getEnvironment().getProperty("test")).isEqualTo("spring"); - assertThat(events.stream().filter(BeforeTestcontainersPropertySuppliedEvent.class::isInstance)).hasSize(1); + assertThat(environment.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 09594242d7b9..b24b25f1a810 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 @@ -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,15 +30,12 @@ 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; /** @@ -83,6 +82,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"); @@ -90,6 +97,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); @@ -116,14 +131,23 @@ void getContainerWhenNotInitializedThrowsException() { } @Test - void getContainerWhenInitializedPublishesEventAndReturnsSuppliedContainer() throws Exception { + void getContainerWhenInitializedReturnsSuppliedContainer() 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" }) @@ -146,6 +170,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/TestContainerConnectionSource.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/TestContainerConnectionSource.java new file mode 100644 index 000000000000..dda8088eceb6 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/TestContainerConnectionSource.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.testcontainers.service.connection; + +import java.util.function.Supplier; + +import org.testcontainers.containers.Container; + +import org.springframework.boot.origin.Origin; +import org.springframework.core.annotation.MergedAnnotation; + +/** + * Factory for tests to create a {@link ContainerConnectionSource}. + * + * @author Phillip Webb + */ +public final class TestContainerConnectionSource { + + private TestContainerConnectionSource() { + } + + public static > ContainerConnectionSource create(String beanNameSuffix, Origin origin, + Class containerType, String containerImageName, MergedAnnotation annotation, + Supplier containerSupplier) { + return new ContainerConnectionSource<>(beanNameSuffix, origin, containerType, containerImageName, annotation, + containerSupplier); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..9fccb9918de8 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryTests.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.hazelcast; + +import com.hazelcast.client.config.ClientConfig; +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 HazelcastContainerConnectionDetailsFactory}. + * + * @author Dmytro Nosan + */ +class HazelcastContainerConnectionDetailsFactoryTests { + + @Test + void shouldRegisterHints() { + RuntimeHints hints = ContainerConnectionDetailsFactoryHints.getRegisteredHints(getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ClientConfig.class)).accepts(hints); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/ClickHouseR2dbcContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/ClickHouseR2dbcContainerConnectionDetailsFactoryTests.java new file mode 100644 index 000000000000..d5a33c094420 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/r2dbc/ClickHouseR2dbcContainerConnectionDetailsFactoryTests.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 ClickHouseR2dbcContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class ClickHouseR2dbcContainerConnectionDetailsFactoryTests { + + @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-tools/spring-boot-antlib/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-antlib/build.gradle index 61dde9509f61..d1cdace201b8 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 @@ -18,7 +18,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-classic")) + compileOnly(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) 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 980049c0cd2d..3a0d4902d9a1 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,7 +58,7 @@ - + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java index 90099cbe8fed..65bf93dcc69e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -280,7 +280,7 @@ private int compare(Object o1, Object o2) { } private boolean isSpringClass(String type) { - return type.startsWith("org.springframework"); + return type.startsWith("org.springframework."); } } 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 ae3cb4dc163e..16d96cb375f5 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 @@ -6,48 +6,19 @@ plugins { 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") - } - // Downgrade Spring Framework as Gradle cannot cope with 6.1.0-M1's - // multi-version jar files with bytecode in META-INF/versions/21 - if (dependency.requested.group.equals("org.springframework")) { - dependency.useVersion("$springFramework60xVersion") - } - // We manage the version of commons-compress here rather than - // in spring-boot-parent to minimize conflicts with Testcontainers - if (dependency.requested.group.equals("org.apache.commons") - && dependency.requested.name.equals("commons-compress")) { - dependency.useVersion("$commonsCompressVersion") - } - // Downgrade Testcontainers for compatibility with the managed - // version of Commons Compress. - if (dependency.requested.group.equals("org.testcontainers")) { - dependency.useVersion("1.19.3") - } - } - } -} - 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:$commonsCompressVersion") - 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") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/dockerTest/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 index 55f0cfda3bbc..4fb4e72c3013 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/dockerTest/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 @@ -36,7 +36,7 @@ class DockerApiIntegrationTests { @Test void pullImage() throws IOException { this.docker.image() - .pull(ImageReference.of("gcr.io/paketo-buildpacks/builder:base"), + .pull(ImageReference.of("gcr.io/paketo-buildpacks/builder:base"), null, new TotalProgressPullListener(new TotalProgressBar("Pulling: "))); } 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 6c4ed5c71a9f..d7b25cccbd17 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 @@ -21,7 +21,9 @@ import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; +import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Image; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; @@ -43,8 +45,12 @@ public void start(BuildRequest request) { } @Override - public Consumer pullingImage(ImageReference imageReference, ImageType imageType) { - return getProgressConsumer(String.format(" > Pulling %s '%s'", imageType.getDescription(), imageReference)); + public Consumer pullingImage(ImageReference imageReference, ImagePlatform platform, + ImageType imageType) { + return (platform != null) + ? getProgressConsumer(" > Pulling %s '%s' for platform '%s'".formatted(imageType.getDescription(), + imageReference, platform)) + : getProgressConsumer(" > Pulling %s '%s'".formatted(imageType.getDescription(), imageReference)); } @Override @@ -113,6 +119,13 @@ public void failedCleaningWorkDir(Cache cache, Exception exception) { log(); } + @Override + public void sensitiveTargetBindingDetected(Binding binding) { + log("Warning: Binding '%s' uses a container path which is used by buildpacks while building. Binding to it can cause problems!" + .formatted(binding)); + 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 2b1f474c06f7..53c134c80009 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.Arrays; import java.util.stream.IntStream; +import org.springframework.boot.buildpack.platform.docker.type.ApiVersion; import org.springframework.util.StringUtils; /** @@ -31,7 +32,7 @@ final class ApiVersions { /** * The platform API versions supported by this release. */ - static final ApiVersions SUPPORTED_PLATFORMS = ApiVersions.of(0, IntStream.rangeClosed(3, 12)); + static final ApiVersions SUPPORTED_PLATFORMS = ApiVersions.of(0, IntStream.rangeClosed(3, 14)); 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 2f21bfa653e8..3c2f5bf28288 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 @@ -21,7 +21,9 @@ import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; +import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Image; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; @@ -46,10 +48,12 @@ public interface BuildLog { /** * Log that an image is being pulled. * @param imageReference the image reference + * @param platform the platform of the image * @param imageType the image type * @return a consumer for progress update events */ - Consumer pullingImage(ImageReference imageReference, ImageType imageType); + Consumer pullingImage(ImageReference imageReference, ImagePlatform platform, + ImageType imageType); /** * Log that an image has been pulled. @@ -122,6 +126,13 @@ public interface BuildLog { */ void failedCleaningWorkDir(Cache cache, Exception exception); + /** + * Log that a binding with a sensitive target has been detected. + * @param binding the binding + * @since 3.4.0 + */ + void sensitiveTargetBindingDetected(Binding binding); + /** * 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 476f0a8917e2..f17e717249fd 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. @@ -27,6 +27,7 @@ import java.util.function.Function; import org.springframework.boot.buildpack.platform.docker.type.Binding; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; 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; @@ -45,9 +46,22 @@ */ 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-java-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-noble-java-tiny"), + ImageReference.of("paketobuildpacks/builder-jammy-java-tiny"), + 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 +69,8 @@ public class BuildRequest { private final ImageReference builder; + private final Boolean trustBuilder; + private final ImageReference runImage; private final Creator creator; @@ -89,12 +105,15 @@ public class BuildRequest { private final List securityOptions; + private final ImagePlatform platform; + 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; @@ -112,16 +131,19 @@ public class BuildRequest { this.createdDate = null; this.applicationDirectory = null; this.securityOptions = null; + this.platform = null; } BuildRequest(ImageReference name, Function applicationContent, ImageReference builder, - ImageReference runImage, Creator creator, Map env, boolean cleanCache, + Boolean trustBuilder, ImageReference runImage, Creator creator, Map env, boolean cleanCache, boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List buildpacks, List bindings, String network, List tags, Cache buildWorkspace, Cache buildCache, - Cache launchCache, Instant createdDate, String applicationDirectory, List securityOptions) { + Cache launchCache, Instant createdDate, String applicationDirectory, List securityOptions, + ImagePlatform platform) { this.name = name; this.applicationContent = applicationContent; this.builder = builder; + this.trustBuilder = trustBuilder; this.runImage = runImage; this.creator = creator; this.env = env; @@ -139,6 +161,7 @@ public class BuildRequest { this.createdDate = createdDate; this.applicationDirectory = applicationDirectory; this.securityOptions = securityOptions; + this.platform = platform; } /** @@ -148,10 +171,25 @@ public class BuildRequest { */ public BuildRequest withBuilder(ImageReference builder) { Assert.notNull(builder, "Builder must not be null"); - return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage, + return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.trustBuilder, + 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, + this.platform); + } + + /** + * 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 + * @since 3.4.0 + */ + public BuildRequest withTrustBuilder(boolean trustBuilder) { + return new BuildRequest(this.name, this.applicationContent, this.builder, trustBuilder, 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); + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -160,10 +198,11 @@ public BuildRequest withBuilder(ImageReference builder) { * @return an updated build request */ 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.buildWorkspace, this.buildCache, - this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, + runImageName.inTaggedOrDigestForm(), 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, this.platform); } /** @@ -173,10 +212,10 @@ public BuildRequest withRunImage(ImageReference runImageName) { */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + 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, this.platform); } /** @@ -190,10 +229,11 @@ public BuildRequest withEnv(String name, String value) { Assert.hasText(value, "Value must not be empty"); Map env = new LinkedHashMap<>(this.env); 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.buildWorkspace, this.buildCache, - this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, Collections.unmodifiableMap(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, + this.platform); } /** @@ -205,10 +245,11 @@ public BuildRequest withEnv(Map env) { Assert.notNull(env, "Env must not be null"); Map updatedEnv = new LinkedHashMap<>(this.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.buildWorkspace, - this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, Collections.unmodifiableMap(updatedEnv), 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, this.platform); } /** @@ -217,10 +258,10 @@ public BuildRequest withEnv(Map env) { * @return an updated build request */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, 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, this.platform); } /** @@ -229,10 +270,10 @@ public BuildRequest withCleanCache(boolean cleanCache) { * @return an updated build request */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, 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, this.platform); } /** @@ -241,10 +282,10 @@ public BuildRequest withVerboseLogging(boolean verboseLogging) { * @return an updated build request */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, + this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, + this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -253,10 +294,10 @@ public BuildRequest withPullPolicy(PullPolicy pullPolicy) { * @return an updated build request */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, + this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, + this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -278,10 +319,10 @@ public BuildRequest withBuildpacks(BuildpackReference... buildpacks) { */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, + this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, + this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -303,10 +344,10 @@ public BuildRequest withBindings(Binding... bindings) { */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, + this.buildpacks, bindings, this.network, this.tags, this.buildWorkspace, this.buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -316,10 +357,10 @@ public BuildRequest withBindings(List bindings) { * @since 2.6.0 */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, + this.buildpacks, this.bindings, network, this.tags, this.buildWorkspace, this.buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -339,10 +380,10 @@ public BuildRequest withTags(ImageReference... tags) { */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, + this.buildpacks, this.bindings, this.network, tags, this.buildWorkspace, this.buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -353,10 +394,10 @@ public BuildRequest withTags(List tags) { */ 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); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, 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.platform); } /** @@ -366,10 +407,10 @@ public BuildRequest withBuildWorkspace(Cache buildWorkspace) { */ 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, this.buildWorkspace, buildCache, this.launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, + this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, buildCache, + this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -379,10 +420,10 @@ public BuildRequest withBuildCache(Cache buildCache) { */ 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.buildWorkspace, this.buildCache, launchCache, this.createdDate, - this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, 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, + launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.platform); } /** @@ -392,10 +433,11 @@ public BuildRequest withLaunchCache(Cache launchCache) { */ 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.buildWorkspace, this.buildCache, this.launchCache, - parseCreatedDate(createdDate), this.applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, 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, parseCreatedDate(createdDate), this.applicationDirectory, this.securityOptions, + this.platform); } private Instant parseCreatedDate(String createdDate) { @@ -417,10 +459,10 @@ private Instant parseCreatedDate(String createdDate) { */ 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.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, - applicationDirectory, this.securityOptions); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, 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, applicationDirectory, this.securityOptions, this.platform); } /** @@ -431,10 +473,25 @@ public BuildRequest withApplicationDirectory(String applicationDirectory) { */ 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); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, 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.platform); + } + + /** + * Return a new {@link BuildRequest} with an updated image platform. + * @param platform the image platform + * @return an updated build request + * @since 3.4.0 + */ + public BuildRequest withImagePlatform(String platform) { + Assert.notNull(platform, "Platform must not be null"); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.trustBuilder, 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, + ImagePlatform.of(platform)); } /** @@ -464,6 +521,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 @@ -604,6 +674,15 @@ public List getSecurityOptions() { return this.securityOptions; } + /** + * Return the platform that should be used when pulling images. + * @return the platform or {@code null} + * @since 3.4.0 + */ + public ImagePlatform getImagePlatform() { + return this.platform; + } + /** * 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 2549144662cd..09baf1db3292 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 @@ -28,7 +28,9 @@ import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; +import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Image; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; 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; @@ -97,9 +99,11 @@ public Builder(BuildLog log, DockerConfiguration dockerConfiguration) { public void build(BuildRequest request) throws DockerEngineException, IOException { Assert.notNull(request, "Request must not be null"); this.log.start(request); + validateBindings(request.getBindings()); String domain = request.getBuilder().getDomain(); PullPolicy pullPolicy = request.getPullPolicy(); - ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy); + ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy, + request.getImagePlatform()); Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder()); BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage); request = withRunImageIfNeeded(request, builderMetadata); @@ -123,6 +127,14 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio } } + private void validateBindings(List bindings) { + for (Binding binding : bindings) { + if (binding.usesSensitiveContainerPath()) { + this.log.sensitiveTargetBindingDetected(binding); + } + } + } + private BuildRequest withRunImageIfNeeded(BuildRequest request, BuilderMetadata metadata) { if (request.getRunImage() != null) { return request; @@ -208,10 +220,13 @@ private class ImageFetcher { private final PullPolicy pullPolicy; - ImageFetcher(String domain, String authHeader, PullPolicy pullPolicy) { + private ImagePlatform defaultPlatform; + + ImageFetcher(String domain, String authHeader, PullPolicy pullPolicy, ImagePlatform platform) { this.domain = domain; this.authHeader = authHeader; this.pullPolicy = pullPolicy; + this.defaultPlatform = platform; } Image fetchImage(ImageType type, ImageReference reference) throws IOException { @@ -236,9 +251,12 @@ Image fetchImage(ImageType type, ImageReference reference) throws IOException { private Image pullImage(ImageReference reference, ImageType imageType) throws IOException { TotalProgressPullListener listener = new TotalProgressPullListener( - Builder.this.log.pullingImage(reference, imageType)); - Image image = Builder.this.docker.image().pull(reference, listener, this.authHeader); + Builder.this.log.pullingImage(reference, this.defaultPlatform, imageType)); + Image image = Builder.this.docker.image().pull(reference, this.defaultPlatform, listener, this.authHeader); Builder.this.log.pulledImage(image, imageType); + if (this.defaultPlatform == null) { + this.defaultPlatform = ImagePlatform.from(image); + } return image; } 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 272099e2456f..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 @@ -130,12 +130,12 @@ private Path createLayerFile(TarArchive tarArchive) throws IOException { try (TarArchiveOutputStream out = new TarArchiveOutputStream(Files.newOutputStream(layerFile))) { try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(sourceTarFile))) { out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - TarArchiveEntry entry = in.getNextTarEntry(); + TarArchiveEntry entry = in.getNextEntry(); while (entry != null) { out.putArchiveEntry(entry); StreamUtils.copy(in, out); out.closeArchiveEntry(); - entry = in.getNextTarEntry(); + entry = in.getNextEntry(); } out.finish(); } 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 3a68b6d99d6d..c2457da089e5 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 @@ -29,6 +29,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost; +import org.springframework.boot.buildpack.platform.docker.type.ApiVersion; 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.ContainerContent; @@ -161,43 +162,104 @@ void execute() throws IOException { if (this.request.isCleanCache()) { deleteCache(this.buildCache); } - run(createPhase()); + 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()); + } 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.withArgs(this.request.getName()); - phase.withBinding(Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); - phase.withBinding(Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); - phase.withBinding(Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); - phase.withBinding(Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE)); - if (this.request.getBindings() != null) { - this.request.getBindings().forEach(phase::withBinding); - } - phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString()); - if (this.request.getNetwork() != null) { - phase.withNetworkMode(this.request.getNetwork()); + phase.withProcessType("web"); } - if (this.request.getCreatedDate() != null) { - phase.withEnv(SOURCE_DATE_EPOCH_KEY, Long.toString(this.request.getCreatedDate().getEpochSecond())); + 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.withImageName(this.request.getName()); + configureOptions(phase); + configureCreatedDate(phase); return phase; } @@ -238,6 +300,7 @@ protected VolumeName createRandomVolumeName(String prefix) { } private void configureDaemonAccess(Phase phase) { + phase.withDaemonAccess(); if (this.dockerHost != null) { if (this.dockerHost.isRemote()) { phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress()); @@ -258,6 +321,22 @@ private void configureDaemonAccess(Phase phase) { } } + 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() { return this.request.isVerboseLogging() && this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION); } @@ -269,7 +348,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); @@ -283,9 +362,9 @@ private void run(Phase phase) throws IOException { } } - private ContainerReference createContainer(ContainerConfig config) throws IOException { - if (this.applicationVolumePopulated) { - return this.docker.container().create(config); + private ContainerReference createContainer(ContainerConfig config, boolean requiresAppUpload) throws IOException { + if (!requiresAppUpload || this.applicationVolumePopulated) { + return this.docker.container().create(config, this.request.getImagePlatform()); } try { if (this.application.getBind() != null) { @@ -293,7 +372,8 @@ private ContainerReference createContainer(ContainerConfig config) throws IOExce } TarArchive applicationContent = this.request.getApplicationContent(this.builder.getBuildOwner()); return this.docker.container() - .create(config, ContainerContent.of(applicationContent, this.applicationDirectory)); + .create(config, this.request.getImagePlatform(), + ContainerContent.of(applicationContent, this.applicationDirectory)); } finally { this.applicationVolumePopulated = true; @@ -339,8 +419,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"; @@ -367,8 +446,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"; @@ -377,8 +455,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"; @@ -387,8 +464,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/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 6e7263af8836..586735fc5c70 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 @@ -28,17 +28,20 @@ import java.util.List; import java.util.Objects; +import org.apache.hc.core5.http.Header; import org.apache.hc.core5.net.URIBuilder; 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.ApiVersion; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; 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.ImagePlatform; 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; @@ -61,7 +64,11 @@ public class DockerApi { private static final List FORCE_PARAMS = Collections.unmodifiableList(Arrays.asList("force", "1")); - static final String API_VERSION = "v1.24"; + static final ApiVersion MINIMUM_API_VERSION = ApiVersion.of(1, 24); + + static final ApiVersion MINIMUM_PLATFORM_API_VERSION = ApiVersion.of(1, 41); + + static final String API_VERSION_HEADER_NAME = "API-Version"; private final HttpTransport http; @@ -73,6 +80,10 @@ public class DockerApi { private final VolumeApi volume; + private final SystemApi system; + + private volatile ApiVersion apiVersion = null; + /** * Create a new {@link DockerApi} instance. */ @@ -100,6 +111,7 @@ public DockerApi(DockerHostConfiguration dockerHost) { this.image = new ImageApi(); this.container = new ContainerApi(); this.volume = new VolumeApi(); + this.system = new SystemApi(); } private HttpTransport http() { @@ -116,7 +128,7 @@ private URI buildUrl(String path, Collection params) { private URI buildUrl(String path, Object... params) { try { - URIBuilder builder = new URIBuilder("/" + API_VERSION + path); + URIBuilder builder = new URIBuilder("/v" + getApiVersion() + path); int param = 0; while (param < params.length) { builder.addParameter(Objects.toString(params[param++]), Objects.toString(params[param++])); @@ -128,6 +140,21 @@ private URI buildUrl(String path, Object... params) { } } + private void verifyApiVersionForPlatform(ImagePlatform platform) { + Assert.isTrue(platform == null || getApiVersion().supports(MINIMUM_PLATFORM_API_VERSION), + () -> "Docker API version must be at least " + MINIMUM_PLATFORM_API_VERSION + + " to support the 'imagePlatform' option, but current API version is " + getApiVersion()); + } + + private ApiVersion getApiVersion() { + ApiVersion apiVersion = this.apiVersion; + if (this.apiVersion == null) { + apiVersion = this.system.getApiVersion(); + this.apiVersion = apiVersion; + } + return apiVersion; + } + /** * Return the Docker API for image operations. * @return the image API @@ -148,6 +175,10 @@ public VolumeApi volume() { return this.volume; } + SystemApi system() { + return this.system; + } + /** * Docker API for image operations. */ @@ -159,27 +190,33 @@ public class ImageApi { /** * Pull an image from a registry. * @param reference the image reference to pull + * @param platform the platform (os/architecture/variant) of the image to pull * @param listener a pull listener to receive update events * @return the {@link ImageApi pulled image} instance * @throws IOException on IO error */ - public Image pull(ImageReference reference, UpdateListener listener) throws IOException { - return pull(reference, listener, null); + public Image pull(ImageReference reference, ImagePlatform platform, + UpdateListener listener) throws IOException { + return pull(reference, platform, listener, null); } /** * Pull an image from a registry. * @param reference the image reference to pull + * @param platform the platform (os/architecture/variant) of the image to pull * @param listener a pull listener to receive update events * @param registryAuth registry authentication credentials * @return the {@link ImageApi pulled image} instance * @throws IOException on IO error */ - public Image pull(ImageReference reference, UpdateListener listener, String registryAuth) - throws IOException { + public Image pull(ImageReference reference, ImagePlatform platform, + UpdateListener listener, String registryAuth) throws IOException { Assert.notNull(reference, "Reference must not be null"); Assert.notNull(listener, "Listener must not be null"); - URI createUri = buildUrl("/images/create", "fromImage", reference); + verifyApiVersionForPlatform(platform); + URI createUri = (platform != null) + ? buildUrl("/images/create", "fromImage", reference, "platform", platform) + : buildUrl("/images/create", "fromImage", reference); DigestCaptureUpdateListener digestCapture = new DigestCaptureUpdateListener(); listener.onStart(); try { @@ -348,22 +385,27 @@ public class ContainerApi { /** * Create a new container a {@link ContainerConfig}. * @param config the container config + * @param platform the platform (os/architecture/variant) of the image the + * container should be created from * @param contents additional contents to include * @return a {@link ContainerReference} for the newly created container * @throws IOException on IO error */ - public ContainerReference create(ContainerConfig config, ContainerContent... contents) throws IOException { + public ContainerReference create(ContainerConfig config, ImagePlatform platform, ContainerContent... contents) + throws IOException { Assert.notNull(config, "Config must not be null"); Assert.noNullElements(contents, "Contents must not contain null elements"); - ContainerReference containerReference = createContainer(config); + ContainerReference containerReference = createContainer(config, platform); for (ContainerContent content : contents) { uploadContainerContent(containerReference, content); } return containerReference; } - private ContainerReference createContainer(ContainerConfig config) throws IOException { - URI createUri = buildUrl("/containers/create"); + private ContainerReference createContainer(ContainerConfig config, ImagePlatform platform) throws IOException { + verifyApiVersionForPlatform(platform); + URI createUri = (platform != null) ? buildUrl("/containers/create", "platform", platform) + : buildUrl("/containers/create"); try (Response response = http().post(createUri, "application/json", config::writeTo)) { return ContainerReference .of(SharedObjectMapper.get().readTree(response.getContent()).at("/Id").asText()); @@ -460,6 +502,39 @@ public void delete(VolumeName name, boolean force) throws IOException { } + /** + * Docker API for system operations. + */ + class SystemApi { + + SystemApi() { + } + + /** + * Get the API version supported by the Docker daemon. + * @return the Docker daemon API version + */ + ApiVersion getApiVersion() { + try { + URI uri = new URIBuilder("/_ping").build(); + try (Response response = http().head(uri)) { + Header apiVersionHeader = response.getHeader(API_VERSION_HEADER_NAME); + if (apiVersionHeader != null) { + return ApiVersion.parse(apiVersionHeader.getValue()); + } + } + catch (Exception ex) { + // fall through to return default value + } + return MINIMUM_API_VERSION; + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + } + /** * {@link UpdateListener} used to capture the image digest. */ 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 index 530482159801..c563b3734f0c 100644 --- 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 @@ -68,13 +68,13 @@ class ExportedImageTar implements Closeable { void exportLayers(IOBiConsumer exports) throws IOException { try (TarArchiveInputStream tar = openTar(this.tarFile)) { - TarArchiveEntry entry = tar.getNextTarEntry(); + TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { TarArchive layerArchive = this.layerArchiveFactory.getLayerArchive(tar, entry); if (layerArchive != null) { exports.accept(entry.getName(), layerArchive); } - entry = tar.getNextTarEntry(); + entry = tar.getNextEntry(); } } } @@ -114,7 +114,7 @@ static LayerArchiveFactory create(ImageReference reference, Path tarFile) throws try (TarArchiveInputStream tar = openTar(tarFile)) { ImageArchiveIndex index = null; ImageArchiveManifest manifest = null; - TarArchiveEntry entry = tar.getNextTarEntry(); + TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { if ("index.json".equals(entry.getName())) { index = ImageArchiveIndex.of(tar); @@ -123,7 +123,7 @@ static LayerArchiveFactory create(ImageReference reference, Path tarFile) throws if ("manifest.json".equals(entry.getName())) { manifest = ImageArchiveManifest.of(tar); } - entry = tar.getNextTarEntry(); + entry = tar.getNextEntry(); } Assert.state(index != null || manifest != null, "Exported image '%s' does not contain 'index.json' or 'manifest.json'".formatted(reference)); @@ -181,12 +181,12 @@ private List getDigestMatches(Path tarFile, Set digests, Set names = digests.stream().map(this::getEntryName).collect(Collectors.toUnmodifiableSet()); List result = new ArrayList<>(); try (TarArchiveInputStream tar = openTar(tarFile)) { - TarArchiveEntry entry = tar.getNextTarEntry(); + TarArchiveEntry entry = tar.getNextEntry(); while (entry != null) { if (names.contains(entry.getName())) { result.add(factory.apply(tar)); } - entry = tar.getNextTarEntry(); + entry = tar.getNextEntry(); } } return Collections.unmodifiableList(result); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java index 6567cdec43b3..8f46104beb25 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java @@ -22,6 +22,7 @@ import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; +import com.fasterxml.jackson.annotation.JsonIgnore; /** * {@link DockerRegistryAuthentication} that uses a Base64 encoded auth header value based * on the JSON created from the instance. @@ -30,6 +31,7 @@ */ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentication { + @JsonIgnore private String authHeader; @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java index c0387e12243b..718912e2a38e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 +26,13 @@ import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.classic.methods.HttpDelete; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; @@ -130,6 +132,16 @@ public Response delete(URI uri) { return execute(new HttpDelete(uri)); } + /** + * Perform an HTTP HEAD operation. + * @param uri the destination URI + * @return the operation response + */ + @Override + public Response head(URI uri) { + return execute(new HttpHead(uri)); + } + private Response execute(HttpUriRequestBase request, String contentType, IOConsumer writer) { request.setEntity(new WritableHttpEntity(contentType, writer)); return execute(request); @@ -257,6 +269,11 @@ public InputStream getContent() throws IOException { return this.response.getEntity().getContent(); } + @Override + public Header getHeader(String name) { + return this.response.getFirstHeader(name); + } + @Override public void close() throws IOException { this.response.close(); 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 c428155142d8..d18617fca571 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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 @@ import java.io.OutputStream; import java.net.URI; +import org.apache.hc.core5.http.Header; + 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; @@ -89,6 +91,14 @@ public interface HttpTransport { */ Response delete(URI uri) throws IOException; + /** + * Perform an HTTP HEAD operation. + * @param uri the destination URI (excluding any host/port) + * @return the operation response + * @throws IOException on IO error + */ + Response head(URI uri) throws IOException; + /** * Create the most suitable {@link HttpTransport} based on the {@link DockerHost}. * @param dockerHost the Docker host information @@ -112,6 +122,10 @@ interface Response extends Closeable { */ InputStream getContent() throws IOException; + default Header getHeader(String name) { + throw new UnsupportedOperationException(); + } + } } 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 f9f6707a5339..67ac077ea064 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 @@ -18,9 +18,8 @@ import java.io.IOException; import java.net.InetAddress; -import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.Socket; -import java.net.UnknownHostException; import com.sun.jna.Platform; import org.apache.hc.client5.http.DnsResolver; @@ -30,24 +29,24 @@ 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.impl.io.DefaultHttpClientConnectionOperator; +import org.apache.hc.client5.http.io.DetachedSocketFactory; 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.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. * * @author Phillip Webb * @author Scott Frederick + * @author Moritz Halbritter */ final class LocalHttpClientTransport extends HttpClientTransport { @@ -62,9 +61,9 @@ private LocalHttpClientTransport(HttpClient client, HttpHost host) { } static LocalHttpClientTransport create(ResolvedDockerHost dockerHost) { - HttpClientBuilder builder = HttpClients.custom(); - builder.setConnectionManager(new LocalConnectionManager(dockerHost.getAddress())); - builder.setRoutePlanner(new LocalRoutePlanner()); + HttpClientBuilder builder = HttpClients.custom() + .setConnectionManager(new LocalConnectionManager(dockerHost)) + .setRoutePlanner(new LocalRoutePlanner()); HttpHost host = new HttpHost(DOCKER_SCHEME, dockerHost.getAddress()); return new LocalHttpClientTransport(builder.build(), host); } @@ -78,65 +77,53 @@ private static class LocalConnectionManager extends BasicHttpClientConnectionMan .setValidateAfterInactivity(TimeValue.NEG_ONE_MILLISECOND) .build(); - LocalConnectionManager(String host) { - super(getRegistry(host), null, null, new LocalDnsResolver()); + LocalConnectionManager(ResolvedDockerHost dockerHost) { + super(new DefaultHttpClientConnectionOperator(new LocalDetachedSocketFactory(dockerHost), null, + new LocalDnsResolver(), (name) -> null), null); setConnectionConfig(CONNECTION_CONFIG); } - private static Registry getRegistry(String host) { - RegistryBuilder builder = RegistryBuilder.create(); - builder.register(DOCKER_SCHEME, new LocalConnectionSocketFactory(host)); - return builder.build(); - } - } /** - * {@link DnsResolver} that ensures only the loopback address is used. + * {@link DetachedSocketFactory} for local Docker. */ - private static final class LocalDnsResolver implements DnsResolver { + static class LocalDetachedSocketFactory implements DetachedSocketFactory { - private static final InetAddress LOOPBACK = InetAddress.getLoopbackAddress(); + private static final String NPIPE_PREFIX = "npipe://"; - @Override - public InetAddress[] resolve(String host) throws UnknownHostException { - return new InetAddress[] { LOOPBACK }; + private final ResolvedDockerHost dockerHost; + + LocalDetachedSocketFactory(ResolvedDockerHost dockerHost) { + this.dockerHost = dockerHost; } @Override - public String resolveCanonicalHostname(String host) throws UnknownHostException { - return LOOPBACK.getCanonicalHostName(); + public Socket create(Proxy proxy) throws IOException { + String address = this.dockerHost.getAddress(); + if (address.startsWith(NPIPE_PREFIX)) { + return NamedPipeSocket.get(address.substring(NPIPE_PREFIX.length())); + } + return (!Platform.isWindows()) ? UnixDomainSocket.get(address) : NamedPipeSocket.get(address); } } /** - * {@link ConnectionSocketFactory} that connects to the local Docker domain socket or - * named pipe. + * {@link DnsResolver} that ensures only the loopback address is used. */ - private static class LocalConnectionSocketFactory implements ConnectionSocketFactory { - - private static final String NPIPE_PREFIX = "npipe://"; - - private final String host; + private static final class LocalDnsResolver implements DnsResolver { - LocalConnectionSocketFactory(String host) { - this.host = host; - } + private static final InetAddress LOOPBACK = InetAddress.getLoopbackAddress(); @Override - public Socket createSocket(HttpContext context) throws IOException { - if (this.host.startsWith(NPIPE_PREFIX)) { - return NamedPipeSocket.get(this.host.substring(NPIPE_PREFIX.length())); - } - return (!Platform.isWindows()) ? DomainSocket.get(this.host) : NamedPipeSocket.get(this.host); + public InetAddress[] resolve(String host) { + return new InetAddress[] { LOOPBACK }; } @Override - public Socket connectSocket(TimeValue connectTimeout, Socket socket, HttpHost host, - InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) - throws IOException { - return socket; + public String resolveCanonicalHostname(String host) { + return LOOPBACK.getCanonicalHostName(); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java index 0ab8d1ef6e79..1b8d84260d85 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/RemoteHttpClientTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 @@ 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.PoolingHttpClientConnectionManagerBuilder; -import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; +import org.apache.hc.client5.http.ssl.TlsSocketStrategy; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.util.Timeout; @@ -74,7 +74,7 @@ private static RemoteHttpClientTransport create(DockerHost host, SslContextFacto .create() .setDefaultSocketConfig(socketConfig); if (host.isSecure()) { - connectionManagerBuilder.setSSLSocketFactory(getSecureConnectionSocketFactory(host, sslContextFactory)); + connectionManagerBuilder.setTlsSocketStrategy(getTlsSocketStrategy(host, sslContextFactory)); } HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(connectionManagerBuilder.build()); @@ -83,13 +83,12 @@ private static RemoteHttpClientTransport create(DockerHost host, SslContextFacto return new RemoteHttpClientTransport(builder.build(), httpHost); } - private static LayeredConnectionSocketFactory getSecureConnectionSocketFactory(DockerHost host, - SslContextFactory sslContextFactory) { + private static TlsSocketStrategy getTlsSocketStrategy(DockerHost host, SslContextFactory sslContextFactory) { String directory = host.getCertificatePath(); Assert.hasText(directory, () -> "Docker host TLS verification requires trust material location to be specified with certificate path"); SSLContext sslContext = sslContextFactory.forDirectory(directory); - return new SSLConnectionSocketFactory(sslContext); + return new DefaultClientTlsStrategy(sslContext); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersion.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ApiVersion.java similarity index 82% rename from spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersion.java rename to spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ApiVersion.java index 667376811326..662680f4bd42 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ApiVersion.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ApiVersion.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.buildpack.platform.build; +package org.springframework.boot.buildpack.platform.docker.type; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,8 +26,9 @@ * * @author Phillip Webb * @author Scott Frederick + * @since 3.4.0 */ -final class ApiVersion { +public final class ApiVersion { private static final Pattern PATTERN = Pattern.compile("^v?(\\d+)\\.(\\d*)$"); @@ -56,27 +57,14 @@ int getMinor() { return this.minor; } - /** - * Assert that this API version supports the specified version. - * @param other the version to check against - * @see #supports(ApiVersion) - */ - void assertSupports(ApiVersion other) { - if (!supports(other)) { - throw new IllegalStateException( - "Detected platform API version '" + other + "' does not match supported version '" + this + "'"); - } - } - /** * Returns if this API version supports the given version. A {@code 0.x} matches only * the same version number. A 1.x or higher release matches when the versions have the * same major version and a minor that is equal or greater. * @param other the version to check against * @return if the specified API version is supported - * @see #assertSupports(ApiVersion) */ - boolean supports(ApiVersion other) { + public boolean supports(ApiVersion other) { if (equals(other)) { return true; } @@ -92,7 +80,7 @@ boolean supports(ApiVersion other) { * @return if any of the specified API versions are supported * @see #supports(ApiVersion) */ - boolean supportsAny(ApiVersion... others) { + public boolean supportsAny(ApiVersion... others) { for (ApiVersion other : others) { if (supports(other)) { return true; @@ -129,7 +117,7 @@ public String toString() { * @return the corresponding {@link ApiVersion} * @throws IllegalArgumentException if the value could not be parsed */ - static ApiVersion parse(String value) { + public static ApiVersion parse(String value) { Assert.hasText(value, "Value must not be empty"); Matcher matcher = PATTERN.matcher(value); Assert.isTrue(matcher.matches(), () -> "Malformed version number '" + value + "'"); @@ -143,7 +131,7 @@ static ApiVersion parse(String value) { } } - static ApiVersion of(int major, int minor) { + public static ApiVersion of(int major, int minor) { return new ApiVersion(major, minor); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Binding.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Binding.java index 8930f4fffda7..7d08240af857 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Binding.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Binding.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,10 @@ package org.springframework.boot.buildpack.platform.docker.type; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.Set; import org.springframework.util.Assert; @@ -24,10 +27,17 @@ * Volume bindings to apply when creating a container. * * @author Scott Frederick + * @author Moritz Halbritter * @since 2.5.0 */ public final class Binding { + /** + * Sensitive container paths, which lead to problems if used in a binding. + */ + private static final Set SENSITIVE_CONTAINER_PATHS = Set.of("/cnb", "/layers", "/workspace", "c:\\cnb", + "c:\\layers", "c:\\workspace"); + private final String value; private Binding(String value) { @@ -55,6 +65,44 @@ public String toString() { return this.value; } + /** + * Whether the binding uses a sensitive container path. + * @return whether the binding uses a sensitive container path + * @since 3.4.0 + */ + public boolean usesSensitiveContainerPath() { + return SENSITIVE_CONTAINER_PATHS.contains(getContainerDestinationPath()); + } + + /** + * Returns the container destination path. + * @return the container destination path + */ + String getContainerDestinationPath() { + List parts = getParts(); + Assert.state(parts.size() >= 2, () -> "Expected 2 or more parts, but found %d".formatted(parts.size())); + return parts.get(1); + } + + private List getParts() { + // Format is ::[] + List parts = new ArrayList<>(); + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < this.value.length(); i++) { + char ch = this.value.charAt(i); + char nextChar = (i + 1 < this.value.length()) ? this.value.charAt(i + 1) : '\0'; + if (ch == ':' && nextChar != '\\') { + parts.add(buffer.toString()); + buffer.setLength(0); + } + else { + buffer.append(ch); + } + } + parts.add(buffer.toString()); + return parts; + } + /** * Create a {@link Binding} with the specified value containing a host source, * container destination, and options. 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 dd224c8dab78..55da2b2f8a4f 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 @@ -31,6 +31,7 @@ * Image details as returned from {@code Docker inspect}. * * @author Phillip Webb + * @author Scott Frederick * @since 2.3.0 */ public class Image extends MappedObject { @@ -43,6 +44,10 @@ public class Image extends MappedObject { private final String os; + private final String architecture; + + private final String variant; + private final String created; Image(JsonNode node) { @@ -51,6 +56,8 @@ public class Image extends MappedObject { this.config = new ImageConfig(getNode().at("/Config")); this.layers = extractLayers(valueAt("/RootFS/Layers", String[].class)); this.os = valueAt("/Os", String.class); + this.architecture = valueAt("/Architecture", String.class); + this.variant = valueAt("/Variant", String.class); this.created = valueAt("/Created", String.class); } @@ -93,6 +100,22 @@ public String getOs() { return (this.os != null) ? this.os : "linux"; } + /** + * Return the architecture of the image. + * @return the image architecture + */ + public String getArchitecture() { + return this.architecture; + } + + /** + * Return the variant of the image. + * @return the image variant + */ + public String getVariant() { + return this.variant; + } + /** * Return the created date of the image. * @return the image created date diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java index ee1ab5cae7d7..8d8145ac4113 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,17 +76,23 @@ public class ImageArchive implements TarArchive { private final String os; + private final String architecture; + + private final String variant; + private final List existingLayers; private final List newLayers; ImageArchive(ObjectMapper objectMapper, ImageConfig imageConfig, Instant createDate, ImageReference tag, String os, - List existingLayers, List newLayers) { + String architecture, String variant, List existingLayers, List newLayers) { this.objectMapper = objectMapper; this.imageConfig = imageConfig; this.createDate = createDate; this.tag = tag; this.os = os; + this.architecture = architecture; + this.variant = variant; this.existingLayers = existingLayers; this.newLayers = newLayers; } @@ -164,11 +170,13 @@ private String writeConfig(Layout writer, List writtenLayers) throws IO private ObjectNode createConfig(List writtenLayers) { ObjectNode config = this.objectMapper.createObjectNode(); - config.set("config", this.imageConfig.getNodeCopy()); - config.set("created", config.textNode(getCreatedDate())); - config.set("history", createHistory(writtenLayers)); - config.set("os", config.textNode(this.os)); - config.set("rootfs", createRootFs(writtenLayers)); + config.set("Config", this.imageConfig.getNodeCopy()); + config.set("Created", config.textNode(getCreatedDate())); + config.set("History", createHistory(writtenLayers)); + config.set("Os", config.textNode(this.os)); + config.set("Architecture", config.textNode(this.architecture)); + config.set("Variant", config.textNode(this.variant)); + config.set("RootFS", createRootFs(writtenLayers)); return config; } @@ -264,7 +272,8 @@ private ImageArchive applyTo(IOConsumer update) throws IOException { update.accept(this); Instant createDate = (this.createDate != null) ? this.createDate : WINDOWS_EPOCH_PLUS_SECOND; return new ImageArchive(SharedObjectMapper.get(), this.config, createDate, this.tag, this.image.getOs(), - this.image.getLayers(), Collections.unmodifiableList(this.newLayers)); + this.image.getArchitecture(), this.image.getVariant(), this.image.getLayers(), + Collections.unmodifiableList(this.newLayers)); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImagePlatform.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImagePlatform.java new file mode 100644 index 000000000000..d43570c117f9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImagePlatform.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.docker.type; + +import java.util.Objects; + +import org.springframework.util.Assert; + +/** + * A platform specification for a Docker image. + * + * @author Scott Frederick + * @since 3.4.0 + */ +public class ImagePlatform { + + private final String os; + + private final String architecture; + + private final String variant; + + ImagePlatform(String os, String architecture, String variant) { + Assert.hasText(os, "OS must not be empty"); + this.os = os; + this.architecture = architecture; + this.variant = variant; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ImagePlatform other = (ImagePlatform) obj; + return Objects.equals(this.architecture, other.architecture) && Objects.equals(this.os, other.os) + && Objects.equals(this.variant, other.variant); + } + + @Override + public int hashCode() { + return Objects.hash(this.architecture, this.os, this.variant); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(this.os); + if (this.architecture != null) { + builder.append("/").append(this.architecture); + } + if (this.variant != null) { + builder.append("/").append(this.variant); + } + return builder.toString(); + } + + /** + * Create a new {@link ImagePlatform} from the given value in the form + * {@code os[/architecture[/variant]]}. + * @param value the value to parse + * @return an {@link ImagePlatform} instance + */ + public static ImagePlatform of(String value) { + Assert.hasText(value, "Value must not be empty"); + String[] split = value.split("/+"); + return switch (split.length) { + case 1 -> new ImagePlatform(split[0], null, null); + case 2 -> new ImagePlatform(split[0], split[1], null); + case 3 -> new ImagePlatform(split[0], split[1], split[2]); + default -> throw new IllegalArgumentException( + "ImagePlatform value '" + value + "' must be in the form of os[/architecture[/variant]]"); + }; + } + + /** + * Create a new {@link ImagePlatform} matching the platform information from the + * provided {@link Image}. + * @param image the image to get platform information from + * @return an {@link ImagePlatform} instance + */ + public static ImagePlatform from(Image image) { + return new ImagePlatform(image.getOs(), image.getArchitecture(), image.getVariant()); + } + +} 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/ApiVersionsTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionsTests.java index 1028f49ea3da..ae46e8c876ae 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionsTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.boot.buildpack.platform.docker.type.ApiVersion; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; 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 6e6fce7f50a8..2d5490f7285a 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,19 @@ 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.ImagePlatform; 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 +69,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 +80,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 +109,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 +119,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 @@ -340,6 +393,13 @@ void withSecurityOptionsSetsSecurityOptions() throws Exception { assertThat(withAppDir.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE"); } + @Test + void withPlatformSetsPlatform() throws Exception { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); + BuildRequest withAppDir = request.withImagePlatform("linux/arm64"); + assertThat(withAppDir.getImagePlatform()).isEqualTo(ImagePlatform.of("linux/arm64")); + } + 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/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 357c0a50f678..239143aa477e 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 @@ -32,10 +32,12 @@ import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; +import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; 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.ImagePlatform; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.TarArchive; @@ -86,9 +88,12 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); @@ -97,9 +102,10 @@ 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)), isNull(), any(), isNull()); then(docker.image()).should() - .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()); + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), isNull()); then(docker.image()).should().load(archive.capture(), any()); then(docker.image()).should().remove(archive.getValue().getTag(), true); then(docker.image()).shouldHaveNoMoreInteractions(); @@ -115,12 +121,12 @@ 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)), isNull(), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); given(docker.image() - .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), - eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); BuildRequest request = getTestRequest().withPublish(true); @@ -129,11 +135,11 @@ 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)), isNull(), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); then(docker.image()).should() - .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), - eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); then(docker.image()).should() .push(eq(request.getName()), any(), eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())); @@ -148,9 +154,12 @@ void buildInvokesBuilderWithDefaultImageTags() throws Exception { DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image-with-no-run-image-tag.json"); Image runImage = loadImage("run-image.json"); - given(docker.image().pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:latest")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:latest")), eq(ImagePlatform.from(builderImage)), + 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")); @@ -168,12 +177,13 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); given(docker.image() .pull(eq(ImageReference .of("docker.io/cloudfoundry/run@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")), - any(), isNull())) + eq(ImagePlatform.from(builderImage)), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); @@ -191,9 +201,12 @@ void buildInvokesBuilderWithNoStack() throws Exception { 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())) + given(docker.image() + .pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + 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")); @@ -211,9 +224,12 @@ void buildInvokesBuilderWithRunImageFromRequest() 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("example.com/custom/run:latest")), eq(ImagePlatform.from(builderImage)), any(), + isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest")); @@ -231,11 +247,14 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + 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); @@ -247,7 +266,7 @@ void buildInvokesBuilderWithNeverPullPolicy() throws Exception { ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); then(docker.image()).should().remove(archive.getValue().getTag(), true); - then(docker.image()).should(never()).pull(any(), any()); + then(docker.image()).should(never()).pull(any(), any(), any()); then(docker.image()).should(times(2)).inspect(any()); } @@ -257,11 +276,14 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + 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); @@ -273,7 +295,7 @@ void buildInvokesBuilderWithAlwaysPullPolicy() throws Exception { ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); then(docker.image()).should().load(archive.capture(), any()); then(docker.image()).should().remove(archive.getValue().getTag(), true); - then(docker.image()).should(times(2)).pull(any(), any(), isNull()); + then(docker.image()).should(times(2)).pull(any(), any(), any(), isNull()); then(docker.image()).should(never()).inspect(any()); } @@ -283,11 +305,14 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + 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); @@ -304,7 +329,7 @@ void buildInvokesBuilderWithIfNotPresentPullPolicy() throws Exception { then(docker.image()).should().load(archive.capture(), any()); then(docker.image()).should().remove(archive.getValue().getTag(), true); then(docker.image()).should(times(2)).inspect(any()); - then(docker.image()).should(times(2)).pull(any(), any(), isNull()); + then(docker.image()).should(times(2)).pull(any(), any(), any(), isNull()); } @Test @@ -313,9 +338,12 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest().withTags(ImageReference.of("my-application:1.2.3")); @@ -339,12 +367,12 @@ 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)), isNull(), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); given(docker.image() - .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), - eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); BuildRequest request = getTestRequest().withPublish(true).withTags(ImageReference.of("my-application:1.2.3")); @@ -354,11 +382,11 @@ 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)), isNull(), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); then(docker.image()).should() - .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), - eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); then(docker.image()).should() .push(eq(request.getName()), any(), eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader())); @@ -372,15 +400,46 @@ void buildInvokesBuilderWithTagsAndPublishesImageAndTags() throws Exception { then(docker.image()).shouldHaveNoMoreInteractions(); } + @Test + void buildInvokesBuilderWithPlatform() throws Exception { + TestPrintStream out = new TestPrintStream(); + ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); + DockerApi docker = mockDockerApi(platform); + Image builderImage = loadImage("image-with-platform.json"); + Image runImage = loadImage("run-image-with-platform.json"); + given(docker.image() + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), eq(platform), any(), isNull())) + .willAnswer(withPulledImage(builderImage)); + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(platform), any(), isNull())) + .willAnswer(withPulledImage(runImage)); + Builder builder = new Builder(BuildLog.to(out), docker, null); + BuildRequest request = getTestRequest().withImagePlatform("linux/arm64/v1"); + 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() + .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), eq(platform), any(), isNull()); + then(docker.image()).should() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(platform), any(), isNull()); + then(docker.image()).should().load(archive.capture(), any()); + then(docker.image()).should().remove(archive.getValue().getTag(), true); + then(docker.image()).shouldHaveNoMoreInteractions(); + } + @Test void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { TestPrintStream out = new TestPrintStream(); 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); @@ -395,9 +454,12 @@ 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)), isNull(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); @@ -413,7 +475,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(), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); @@ -431,7 +493,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(), any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) .willAnswer(withPulledImage(builderImage)); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); @@ -447,9 +509,10 @@ 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(), any(), isNull())) .willAnswer(withPulledImage(builderImage)); - given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) + given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), any(), isNull())) .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack@1.2.3"); @@ -459,10 +522,34 @@ void buildWhenRequestedBuildpackNotInBuilderThrowsException() throws Exception { .withMessageContaining("not found in builder"); } + @Test + void logsWarningIfBindingWithSensitiveTargetIsDetected() throws IOException { + 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_REF)), isNull(), any(), isNull())) + .willAnswer(withPulledImage(builderImage)); + given(docker.image() + .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)), + any(), isNull())) + .willAnswer(withPulledImage(runImage)); + Builder builder = new Builder(BuildLog.to(out), docker, null); + BuildRequest request = getTestRequest().withBindings(Binding.from("/host", "/cnb")); + builder.build(request); + assertThat(out.toString()).contains( + "Warning: Binding '/host:/cnb' uses a container path which is used by buildpacks while building. Binding to it can cause problems!"); + } + private DockerApi mockDockerApi() throws IOException { + return mockDockerApi(null); + } + + private DockerApi mockDockerApi(ImagePlatform platform) throws IOException { ContainerApi containerApi = mock(ContainerApi.class); ContainerReference reference = ContainerReference.of("container-ref"); - given(containerApi.create(any(), any())).willReturn(reference); + given(containerApi.create(any(), eq(platform), any())).willReturn(reference); given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(0, null)); ImageApi imageApi = mock(ImageApi.class); VolumeApi volumeApi = mock(VolumeApi.class); @@ -476,7 +563,7 @@ private DockerApi mockDockerApi() throws IOException { private DockerApi mockDockerApiLifecycleError() throws IOException { ContainerApi containerApi = mock(ContainerApi.class); ContainerReference reference = ContainerReference.of("container-ref"); - given(containerApi.create(any(), any())).willReturn(reference); + given(containerApi.create(any(), isNull(), any())).willReturn(reference); given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(9, null)); ImageApi imageApi = mock(ImageApi.class); VolumeApi volumeApi = mock(VolumeApi.class); @@ -490,7 +577,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 { @@ -499,7 +586,7 @@ private Image loadImage(String name) throws IOException { private Answer withPulledImage(Image image) { return (invocation) -> { - TotalProgressPullListener listener = invocation.getArgument(1, TotalProgressPullListener.class); + TotalProgressPullListener listener = invocation.getArgument(2, TotalProgressPullListener.class); listener.onStart(); listener.onFinish(); return image; 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 b10b0f0c7608..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 @@ -218,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 6f898d1a9817..47413d02f49d 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. @@ -34,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; @@ -48,11 +49,13 @@ import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; 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.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; @@ -60,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -87,22 +91,32 @@ void setup() { this.docker = mockDockerApi(); } - @Test - void executeExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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'"); } @Test 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().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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")); @@ -111,182 +125,300 @@ void executeWithBindingsExecutesPhases() throws Exception { @Test 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().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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 { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeOnlyUploadsContentOnce(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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 { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWhenAlreadyRunThrowsException(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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 { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWhenBuilderReturnsErrorThrowsException(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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 { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWhenCleanCacheClearsCache(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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); } @Test void executeWhenPlatformApiNotSupportedThrowsException() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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"); } @Test void executeWhenMultiplePlatformApisNotSupportedThrowsException() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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 { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWhenMultiplePlatformApisSupportedExecutesPhase(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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); } @Test 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().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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 { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithCacheVolumeNamesExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withBuildWorkspace(Cache.volume("work-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 executeWithCacheBindMountsExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithCacheBindMountsExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withBuildWorkspace(Cache.bind("/tmp/work")) + 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-cache-bind-mounts.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 executeWithCreatedDateExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithCreatedDateExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), 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).withCreatedDate("2020-07-01T12:34:56Z"); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-created-date.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 executeWithApplicationDirectoryExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithApplicationDirectoryExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withApplicationDirectory("/application"); + BuildRequest request = getTestRequest(trustBuilder).withApplicationDirectory("/application"); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json")); + 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 executeWithSecurityOptionsExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithSecurityOptionsExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest().withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); + BuildRequest request = getTestRequest(trustBuilder) + .withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); createLifecycle(request).execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-security-opts.json", true)); + 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'"); } - @Test - void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithDockerHostAndRemoteAddressExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest(); + BuildRequest request = getTestRequest(trustBuilder); createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"))) .execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); + 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'"); } - @Test - void executeWithDockerHostAndLocalAddressExecutesPhases() throws Exception { - given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); - given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); + @ParameterizedTest + @BooleanValueSource + void executeWithDockerHostAndLocalAddressExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); - BuildRequest request = getTestRequest(); + BuildRequest request = getTestRequest(trustBuilder); createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock"))) .execute(); - assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); + 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'"); + } + + @ParameterizedTest + @BooleanValueSource + void executeWithImagePlatformExecutesPhases(boolean trustBuilder) throws Exception { + given(this.docker.container().create(any(), eq(ImagePlatform.of("linux/arm64")))) + .willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().create(any(), eq(ImagePlatform.of("linux/arm64")), any())) + .willAnswer(answerWithGeneratedContainerId()); + given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); + BuildRequest request = getTestRequest(trustBuilder).withImagePlatform("linux/arm64"); + createLifecycle(request).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'"); } @@ -301,14 +433,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 { @@ -316,9 +450,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 { @@ -349,8 +483,8 @@ private Answer answerWithGeneratedContainerId() { ArrayNode command = getCommand(config); String name = command.get(0).asText().substring(1).replaceAll("/", "-"); this.configs.put(name, config); - if (invocation.getArguments().length > 1) { - this.content.put(name, invocation.getArgument(1, ContainerContent.class)); + if (invocation.getArguments().length > 2) { + this.content.put(name, invocation.getArgument(2, ContainerContent.class)); } return ContainerReference.of(name); }; 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 1e25ed10a998..f1dbd3fd21ba 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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.buildpack.platform.docker.LogUpdateEvent; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.docker.type.Image; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; import org.springframework.util.FileCopyUtils; @@ -51,6 +52,7 @@ void printsExpectedOutput() throws Exception { BuildRequest request = mock(BuildRequest.class); ImageReference name = ImageReference.of("my-app:latest"); ImageReference builderImageReference = ImageReference.of("cnb/builder"); + ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); Image builderImage = mock(Image.class); given(builderImage.getDigests()).willReturn(Collections.singletonList("00000001")); ImageReference runImageReference = ImageReference.of("cnb/runner"); @@ -60,11 +62,12 @@ void printsExpectedOutput() throws Exception { ImageReference tag = ImageReference.of("my-app:1.0"); given(request.getTags()).willReturn(Collections.singletonList(tag)); log.start(request); - Consumer pullBuildImageConsumer = log.pullingImage(builderImageReference, + Consumer pullBuildImageConsumer = log.pullingImage(builderImageReference, null, ImageType.BUILDER); pullBuildImageConsumer.accept(new TotalProgressEvent(100)); log.pulledImage(builderImage, ImageType.BUILDER); - Consumer pullRunImageConsumer = log.pullingImage(runImageReference, ImageType.RUNNER); + Consumer pullRunImageConsumer = log.pullingImage(runImageReference, platform, + ImageType.RUNNER); pullRunImageConsumer.accept(new TotalProgressEvent(100)); log.pulledImage(runImage, ImageType.RUNNER); log.executingLifecycle(request, LifecycleVersion.parse("0.5"), Cache.volume(VolumeName.of("pack-abc.cache"))); 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 69a32a82f4de..619f23ea2c13 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 @@ -18,15 +18,19 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -39,15 +43,18 @@ 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.SystemApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; 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.ApiVersion; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerContent; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; 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.ImagePlatform; 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.Content; @@ -80,12 +87,18 @@ @ExtendWith(MockitoExtension.class) class DockerApiTests { - private static final String API_URL = "/" + DockerApi.API_VERSION; + private static final String API_URL = "/v" + DockerApi.MINIMUM_API_VERSION; + + public static final String PING_URL = "/_ping"; private static final String IMAGES_URL = API_URL + "/images"; + private static final String IMAGES_1_41_URL = "/v" + ApiVersion.of(1, 41) + "/images"; + private static final String CONTAINERS_URL = API_URL + "/containers"; + private static final String CONTAINERS_1_41_URL = "/v" + ApiVersion.of(1, 41) + "/containers"; + private static final String VOLUMES_URL = API_URL + "/volumes"; @Mock @@ -124,6 +137,29 @@ public InputStream getContent() { }; } + private Response responseWithHeaders(Header... headers) { + return new Response() { + + @Override + public InputStream getContent() { + return null; + } + + @Override + public Header getHeader(String name) { + return Arrays.stream(headers) + .filter((header) -> header.getName().equals(name)) + .findFirst() + .orElse(null); + } + + @Override + public void close() { + } + + }; + } + @Test void createDockerApi() { DockerApi api = new DockerApi(); @@ -154,13 +190,14 @@ void setup() { @Test void pullWhenReferenceIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(null, this.pullListener)) + assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(null, null, this.pullListener)) .withMessage("Reference must not be null"); } @Test void pullWhenListenerIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(ImageReference.of("ubuntu"), null)) + assertThatIllegalArgumentException() + .isThrownBy(() -> this.api.pull(ImageReference.of("ubuntu"), null, null)) .withMessage("Listener must not be null"); } @@ -171,7 +208,7 @@ void pullPullsImageAndProducesEvents() throws Exception { URI imageUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/json"); given(http().post(eq(createUri), isNull())).willReturn(responseOf("pull-stream.json")); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); - Image image = this.api.pull(reference, this.pullListener); + Image image = this.api.pull(reference, null, this.pullListener); assertThat(image.getLayers()).hasSize(46); InOrder ordered = inOrder(this.pullListener); ordered.verify(this.pullListener).onStart(); @@ -186,7 +223,26 @@ void pullWithRegistryAuthPullsImageAndProducesEvents() throws Exception { URI imageUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/json"); given(http().post(eq(createUri), eq("auth token"))).willReturn(responseOf("pull-stream.json")); given(http().get(imageUri)).willReturn(responseOf("type/image.json")); - Image image = this.api.pull(reference, this.pullListener, "auth token"); + Image image = this.api.pull(reference, null, this.pullListener, "auth token"); + assertThat(image.getLayers()).hasSize(46); + InOrder ordered = inOrder(this.pullListener); + ordered.verify(this.pullListener).onStart(); + ordered.verify(this.pullListener, times(595)).onUpdate(any()); + ordered.verify(this.pullListener).onFinish(); + } + + @Test + void pullWithPlatformPullsImageAndProducesEvents() throws Exception { + ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); + ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); + URI createUri = new URI(IMAGES_1_41_URL + + "/create?fromImage=gcr.io%2Fpaketo-buildpacks%2Fbuilder%3Abase&platform=linux%2Farm64%2Fv1"); + URI imageUri = new URI(IMAGES_1_41_URL + "/gcr.io/paketo-buildpacks/builder:base/json"); + given(http().head(eq(new URI(PING_URL)))) + .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.41"))); + given(http().post(eq(createUri), isNull())).willReturn(responseOf("pull-stream.json")); + given(http().get(imageUri)).willReturn(responseOf("type/image.json")); + Image image = this.api.pull(reference, platform, this.pullListener); assertThat(image.getLayers()).hasSize(46); InOrder ordered = inOrder(this.pullListener); ordered.verify(this.pullListener).onStart(); @@ -194,6 +250,17 @@ void pullWithRegistryAuthPullsImageAndProducesEvents() throws Exception { ordered.verify(this.pullListener).onFinish(); } + @Test + void pullWithPlatformAndInsufficientApiVersionThrowsException() throws Exception { + ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); + ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); + given(http().head(eq(new URI(PING_URL)))).willReturn(responseWithHeaders( + new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, DockerApi.MINIMUM_API_VERSION))); + assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(reference, platform, this.pullListener)) + .withMessageContaining("must be at least 1.41") + .withMessageContaining("current API version is 1.24"); + } + @Test void pushWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.push(null, this.pushListener, null)) @@ -338,10 +405,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(); } } }); @@ -366,10 +433,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(); } } }); @@ -460,7 +527,7 @@ void setup() { @Test void createWhenConfigIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(null)) + assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(null, null)) .withMessage("Config must not be null"); } @@ -471,7 +538,7 @@ void createCreatesContainer() throws Exception { URI createUri = new URI(CONTAINERS_URL + "/create"); given(http().post(eq(createUri), eq("application/json"), any())) .willReturn(responseOf("create-container-response.json")); - ContainerReference containerReference = this.api.create(config); + ContainerReference containerReference = this.api.create(config, null); assertThat(containerReference).hasToString("e90e34656806"); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -493,7 +560,7 @@ void createWhenHasContentContainerWithContent() throws Exception { .willReturn(responseOf("create-container-response.json")); URI uploadUri = new URI(CONTAINERS_URL + "/e90e34656806/archive?path=%2F"); given(http().put(eq(uploadUri), eq("application/x-tar"), any())).willReturn(emptyResponse()); - ContainerReference containerReference = this.api.create(config, content); + ContainerReference containerReference = this.api.create(config, null, content); assertThat(containerReference).hasToString("e90e34656806"); then(http()).should().post(any(), any(), this.writer.capture()); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -504,6 +571,34 @@ void createWhenHasContentContainerWithContent() throws Exception { assertThat(out.toByteArray()).hasSizeGreaterThan(2000); } + @Test + void createWithPlatformCreatesContainer() throws Exception { + ImageReference imageReference = ImageReference.of("ubuntu:bionic"); + ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); + ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); + given(http().head(eq(new URI(PING_URL)))) + .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.41"))); + URI createUri = new URI(CONTAINERS_1_41_URL + "/create?platform=linux%2Farm64%2Fv1"); + given(http().post(eq(createUri), eq("application/json"), any())) + .willReturn(responseOf("create-container-response.json")); + ContainerReference containerReference = this.api.create(config, platform); + assertThat(containerReference).hasToString("e90e34656806"); + then(http()).should().post(any(), any(), this.writer.capture()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + this.writer.getValue().accept(out); + assertThat(out.toByteArray()).hasSize(config.toString().length()); + } + + @Test + void createWithPlatformAndInsufficientApiVersionThrowsException() throws Exception { + ImageReference imageReference = ImageReference.of("ubuntu:bionic"); + ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash")); + ImagePlatform platform = ImagePlatform.of("linux/arm64/v1"); + assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(config, platform)) + .withMessageContaining("must be at least 1.41") + .withMessageContaining("current API version is 1.24"); + } + @Test void startWhenReferenceIsNullThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> this.api.start(null)) @@ -621,4 +716,42 @@ void deleteWhenForceIsTrueDeletesContainer() throws Exception { } + @Nested + class SystemDockerApiTests { + + private SystemApi api; + + @BeforeEach + void setup() { + this.api = DockerApiTests.this.dockerApi.system(); + } + + @Test + void getApiVersionWithVersionHeaderReturnsVersion() throws Exception { + given(http().head(eq(new URI(PING_URL)))) + .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.44"))); + assertThat(this.api.getApiVersion()).isEqualTo(ApiVersion.of(1, 44)); + } + + @Test + void getApiVersionWithEmptyVersionHeaderReturnsDefaultVersion() throws Exception { + given(http().head(eq(new URI(PING_URL)))) + .willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, ""))); + assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.MINIMUM_API_VERSION); + } + + @Test + void getApiVersionWithNoVersionHeaderReturnsDefaultVersion() throws Exception { + given(http().head(eq(new URI(PING_URL)))).willReturn(emptyResponse()); + assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.MINIMUM_API_VERSION); + } + + @Test + void getApiVersionWithExceptionReturnsDefaultVersion() throws Exception { + given(http().head(eq(new URI(PING_URL)))).willThrow(new IOException("simulated error")); + assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.MINIMUM_API_VERSION); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ApiVersionTests.java similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionTests.java rename to spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ApiVersionTests.java index 1afb601232b0..d06d315eebf8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/ApiVersionTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ApiVersionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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.buildpack.platform.build; +package org.springframework.boot.buildpack.platform.docker.type; import java.util.Arrays; @@ -22,7 +22,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; /** * Tests for {@link ApiVersion}. @@ -57,18 +56,6 @@ void parseReturnsVersion() { assertThat(version.getMinor()).isEqualTo(2); } - @Test - void assertSupportsWhenSupports() { - ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.0")); - } - - @Test - void assertSupportsWhenDoesNotSupportThrowsException() { - assertThatIllegalStateException() - .isThrownBy(() -> ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.3"))) - .withMessage("Detected platform API version '1.3' does not match supported version '1.2'"); - } - @Test void supportsWhenSame() { assertThat(supports("0.0", "0.0")).isTrue(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/BindingTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/BindingTests.java index 3f0ec68cbea8..7f8d29c269f5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/BindingTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/BindingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,18 @@ package org.springframework.boot.buildpack.platform.docker.type; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link Binding}. * * @author Scott Frederick + * @author Moritz Halbritter */ class BindingTests { @@ -70,4 +74,51 @@ void fromVolumeNameSourceWithNullSourceThrowsException() { .withMessageContaining("SourceVolume must not be null"); } + @Test + void shouldReturnContainerDestinationPath() { + Binding binding = Binding.from("/host", "/container"); + assertThat(binding.getContainerDestinationPath()).isEqualTo("/container"); + } + + @Test + void shouldReturnContainerDestinationPathWithOptions() { + Binding binding = Binding.of("/host:/container:ro"); + assertThat(binding.getContainerDestinationPath()).isEqualTo("/container"); + } + + @Test + void shouldReturnContainerDestinationPathOnWindows() { + Binding binding = Binding.from("C:\\host", "C:\\container"); + assertThat(binding.getContainerDestinationPath()).isEqualTo("C:\\container"); + } + + @Test + void shouldReturnContainerDestinationPathOnWindowsWithOptions() { + Binding binding = Binding.of("C:\\host:C:\\container:ro"); + assertThat(binding.getContainerDestinationPath()).isEqualTo("C:\\container"); + } + + @Test + void shouldFailIfBindingIsMalformed() { + Binding binding = Binding.of("some-invalid-binding"); + assertThatIllegalStateException().isThrownBy(binding::getContainerDestinationPath) + .withMessage("Expected 2 or more parts, but found 1"); + } + + @ParameterizedTest + @CsvSource(textBlock = """ + /cnb, true + /layers, true + /workspace, true + /something, false + c:\\cnb, true + c:\\layers, true + c:\\workspace, true + c:\\something, false + """) + void shouldDetectSensitiveContainerPaths(String containerPath, boolean sensitive) { + Binding binding = Binding.from("/host", containerPath); + assertThat(binding.usesSensitiveContainerPath()).isEqualTo(sensitive); + } + } 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..3d303df98d5b 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,13 +72,13 @@ 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/"); } } private void assertExpectedConfig(TarArchiveEntry entry, byte[] content) throws Exception { - assertThat(entry.getName()).isEqualTo("682f8d24b9d9c313d1190a0e955dcb5e65ec9beea40420999839c6f0cbb38382.json"); + assertThat(entry.getName()).isEqualTo("416c76dc7f691f91e80516ff039e056f32f996b59af4b1cb8114e6ae8171a374.json"); String actualJson = new String(content, StandardCharsets.UTF_8); String expectedJson = StreamUtils.copyToString(getContent("image-archive-config.json"), StandardCharsets.UTF_8); JSONAssert.assertEquals(expectedJson, actualJson, false); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImagePlatformTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImagePlatformTests.java new file mode 100644 index 000000000000..46da80ad2399 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImagePlatformTests.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.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; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class ImagePlatformTests extends AbstractJsonTests { + + @Test + void ofWithOsParses() { + ImagePlatform platform = ImagePlatform.of("linux"); + assertThat(platform.toString()).isEqualTo("linux"); + } + + @Test + void ofWithOsAndArchitectureParses() { + ImagePlatform platform = ImagePlatform.of("linux/amd64"); + assertThat(platform.toString()).isEqualTo("linux/amd64"); + } + + @Test + void ofWithOsAndArchitectureAndVariantParses() { + ImagePlatform platform = ImagePlatform.of("linux/amd64/v1"); + assertThat(platform.toString()).isEqualTo("linux/amd64/v1"); + } + + @Test + void ofWithEmptyValueFails() { + assertThatIllegalArgumentException().isThrownBy(() -> ImagePlatform.of("")) + .withMessageContaining("Value must not be empty"); + } + + @Test + void ofWithTooManySegmentsFails() { + assertThatIllegalArgumentException().isThrownBy(() -> ImagePlatform.of("linux/amd64/v1/extra")) + .withMessageContaining("value 'linux/amd64/v1/extra'"); + } + + @Test + void fromImageMatchesImage() throws IOException { + ImagePlatform platform = ImagePlatform.from(getImage()); + assertThat(platform.toString()).isEqualTo("linux/amd64/v1"); + } + + private Image getImage() throws IOException { + return Image.of(getContent("image.json")); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageTests.java index 851a9c781a7a..2bcea073ecda 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,18 @@ void getOsReturnsOs() throws Exception { assertThat(image.getOs()).isEqualTo("linux"); } + @Test + void getArchitectureReturnsArchitecture() throws Exception { + Image image = getImage(); + assertThat(image.getArchitecture()).isEqualTo("amd64"); + } + + @Test + void getVariantReturnsVariant() throws Exception { + Image image = getImage(); + assertThat(image.getVariant()).isEqualTo("v1"); + } + @Test void getCreatedReturnsDate() throws Exception { Image image = getImage(); 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/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-platform.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-platform.json new file mode 100644 index 000000000000..715d3ea4b73b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-platform.json @@ -0,0 +1,133 @@ +{ + "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\":\"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\"}]}]", + "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", + "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" + } + }, + "Architecture": "arm64", + "Os": "linux", + "Variant": "v1", + "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 index 7259fc11af77..7c7c285d58d5 100644 --- 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 @@ -26,11 +26,11 @@ }, "HostConfig": { "Binds": [ - "/var/run/docker.sock:/var/run/docker.sock", - "/tmp/work-layers:/layers", "/tmp/work-app:/workspace", + "/tmp/work-layers:/layers", "/tmp/build-cache:/cache", - "/tmp/launch-cache:/launch-cache" + "/tmp/launch-cache:/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-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 0f611d5d059c..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", - "work-volume-layers:/layers", "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 index c47bd7f9ffd7..4f1a1e75fb2b 100644 --- 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 @@ -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=user:USER", 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/build/print-stream-build-log.txt b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/print-stream-build-log.txt index 6fcfc7ee2c01..b2d73f7292c1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/print-stream-build-log.txt +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/print-stream-build-log.txt @@ -2,7 +2,7 @@ Building image 'docker.io/library/my-app:latest' > Pulling builder image 'docker.io/cnb/builder' .................................................. > Pulled builder image '00000001' - > Pulling run image 'docker.io/cnb/runner' .................................................. + > Pulling run image 'docker.io/cnb/runner' for platform 'linux/arm64/v1' .................................................. > Pulled run image '00000002' > Executing lifecycle version v0.5.0 > Using build cache volume 'pack-abc.cache' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/run-image-with-platform.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/run-image-with-platform.json new file mode 100644 index 000000000000..0135acd1c08f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/run-image-with-platform.json @@ -0,0 +1,98 @@ +{ + "Id": "sha256:1332879bc8e38793a45ebe5a750f2a1c35df07ec2aa9c18f694644a9de77359b", + "RepoTags": [ + "cloudfoundry/run:base-cnb" + ], + "RepoDigests": [ + "cloudfoundry/run@sha256:fb5ecb90a42b2067a859aab23fc1f5e9d9c2589d07ba285608879e7baa415aad" + ], + "Parent": "", + "Comment": "", + "Created": "2020-03-20T20:18:18.117972538Z", + "Container": "91d1af87c3bb6163cd9c7cb21e6891cd25f5fa3c7417779047776e288c0bc234", + "ContainerConfig": { + "Hostname": "91d1af87c3bb", + "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" + ], + "Cmd": [ + "/bin/sh", + "-c", + "#(nop) ", + "LABEL io.buildpacks.stack.id=io.buildpacks.stacks.bionic" + ], + "ArgsEscaped": true, + "Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": { + "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic" + } + }, + "DockerVersion": "18.09.6", + "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" + ], + "Cmd": [ + "/bin/bash" + ], + "ArgsEscaped": true, + "Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": { + "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic" + } + }, + "Architecture": "arm64", + "Os": "linux", + "Variant": "v1", + "Size": 71248531, + "VirtualSize": 71248531, + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/docker/overlay2/17f0a4530fbc3e2982f9dc8feb8c8ddc124473bdd50130dae20856ac597d82dd/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/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/merged", + "UpperDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/diff", + "WorkDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/work" + }, + "Name": "overlay2" + }, + "RootFS": { + "Type": "layers", + "Layers": [ + "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", + "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", + "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", + "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", + "sha256:c1daeb79beb276c7441d9a1d7281433e9a7edb9f652b8996ecc62b51e88a47b2", + "sha256:eb195d29dc1aa6e4239f00e7868deebc5ac12bebe76104e0b774c1ef29ca78e3" + ] + }, + "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/docker/type/image-archive-config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-config.json index 395bbcda70b1..fedefec5d78a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-config.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-config.json @@ -1,5 +1,5 @@ { - "config": { + "Config": { "Hostname": "", "Domainname": "", "User": "vcap", @@ -25,8 +25,8 @@ "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, - "created": "1980-01-01T00:00:01Z", - "history": [ + "Created": "1980-01-01T00:00:01Z", + "History": [ { }, @@ -169,8 +169,10 @@ } ], - "os": "linux", - "rootfs": { + "Architecture": "amd64", + "Os": "linux", + "Variant": "v1", + "RootFS": { "diff_ids": [ "sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342", "sha256:7755b972f0b4f49de73ef5114fb3ba9c69d80f217e80da99f56f0d0a5dcb3d70", 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-manifest.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-manifest.json index 4830d4e8e1c3..129b9cb90895 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-manifest.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image-archive-manifest.json @@ -1,6 +1,6 @@ [ { - "Config": "682f8d24b9d9c313d1190a0e955dcb5e65ec9beea40420999839c6f0cbb38382.json", + "Config": "416c76dc7f691f91e80516ff039e056f32f996b59af4b1cb8114e6ae8171a374.json", "Layers": [ "blank_0", "blank_1", diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image.json index 8fbaf3155686..901e3b90f5d0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/image.json @@ -72,9 +72,10 @@ "io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3" } }, - "Architecture": "amd64", "Os": "linux", - "Size": 1559461360, + "Architecture": "amd64", + "Variant": "v1", + "Size": 1559461360, "VirtualSize": 1559461360, "GraphDriver": { "Data": { 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 312efac14343..5c60bf8703cf 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 @@ -34,7 +34,7 @@ dependencies { intTestImplementation("org.junit.jupiter:junit-jupiter") intTestImplementation("org.springframework:spring-core") - loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-classic")) + loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) testImplementation(project(":spring-boot-project:spring-boot")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) 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-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 index 92ffcfada218..a87c6bb47d80 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,11 +68,11 @@ void write(Changelog changelog) { 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", newVersionNumber); + write("%n%n%n== Deprecated in %s%n%n", newVersionNumber); writeDeprecated(differencesByType.get(DifferenceType.DEPRECATED)); - write("%n%n%n== Added in %s%n", newVersionNumber); + write("%n%n%n== Added in %s%n%n", newVersionNumber); writeAdded(differencesByType.get(DifferenceType.ADDED)); - write("%n%n%n== Removed in %s%n", newVersionNumber); + write("%n%n%n== Removed in %s%n%n", newVersionNumber); writeRemoved(differencesByType.get(DifferenceType.DELETED), differencesByType.get(DifferenceType.DEPRECATED)); } 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 index ac5cc843e16f..26876aa85426 100644 --- 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 @@ -3,11 +3,13 @@ Configuration property changes between `1.0` and `2.0` == Deprecated in 2.0 + _None_. == Added in 2.0 + |====================== | Key | Default value | Description @@ -19,6 +21,7 @@ _None_. == Removed in 2.0 + |====================== | Key | Replacement | Reason 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 6980c68430a8..240281c2650e 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 @@ -26,4 +26,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/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 a6b084a8cc7f..e44bd5e54f60 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 @@ -21,13 +21,12 @@ import java.io.StringWriter; import java.time.Duration; import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Collections; import java.util.Deque; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.annotation.processing.AbstractProcessor; @@ -48,6 +47,7 @@ import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.InvalidConfigurationMetadataException; +import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; /** @@ -59,6 +59,7 @@ * @author Kris De Volder * @author Jonas Keßler * @author Scott Frederick + * @author Moritz Halbritter * @since 1.2.0 */ @SupportedAnnotationTypes({ ConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION, @@ -106,8 +107,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String NAME_ANNOTATION = "org.springframework.boot.context.properties.bind.Name"; - private static final Set SUPPORTED_OPTIONS = Collections - .unmodifiableSet(Collections.singleton(ADDITIONAL_METADATA_LOCATIONS_OPTION)); + static final String ENDPOINT_ACCESS_ENUM = "org.springframework.boot.actuate.endpoint.Access"; + + private static final Set SUPPORTED_OPTIONS = Set.of(ADDITIONAL_METADATA_LOCATIONS_OPTION); private MetadataStore metadataStore; @@ -140,8 +142,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() { @@ -152,6 +154,10 @@ protected String nameAnnotation() { return NAME_ANNOTATION; } + protected String endpointAccessEnum() { + return ENDPOINT_ACCESS_ENUM; + } + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -294,18 +300,48 @@ 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 enabledByDefaultAttribute = (boolean) elementValues.getOrDefault("enableByDefault", true); + String defaultAccess = (!enabledByDefaultAttribute) ? "none" + : (elementValues.getOrDefault("defaultAccess", "unrestricted").toString()).toLowerCase(Locale.ENGLISH); + boolean enabledByDefault = !"none".equals(defaultAccess) && enabledByDefaultAttribute; 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)); + ItemMetadata accessProperty = ItemMetadata.newProperty(endpointKey, "access", endpointAccessEnum(), type, null, + "Permitted level of access for the %s endpoint.".formatted(endpointId), defaultAccess, null); + this.metadataCollector.add( + ItemMetadata.newProperty(endpointKey, "enabled", Boolean.class.getName(), type, null, + "Whether to enable the %s endpoint.".formatted(endpointId), enabledByDefault, + new ItemDeprecation(null, accessProperty.getName(), "3.4.0")), + (existing) -> checkEnabledValueMatchesExisting(existing, enabledByDefault, type)); + this.metadataCollector.add(accessProperty, + (existing) -> checkDefaultAccessValueMatchesExisting(existing, defaultAccess, 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 void checkDefaultAccessValueMatchesExisting(ItemMetadata existing, String defaultAccess, + String sourceType) { + String existingDefaultAccess = (String) existing.getDefaultValue(); + if (!Objects.equals(defaultAccess, existingDefaultAccess)) { + 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(), existingDefaultAccess, sourceType, + defaultAccess)); + } + } + private boolean hasMainReadOperation(TypeElement element) { for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) { if (this.metadataEnv.getReadOperationAnnotation(method) != null 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 6d4cc6c3d2ac..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. @@ -50,30 +50,22 @@ * * @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; @@ -162,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; } 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 c4808a93a71f..1f1853de8714 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,78 @@ 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 String getParameterName(VariableElement parameter) { + private PropertyDescriptor extracted(TypeElement declaringElement, TypeElementMembers members, + VariableElement parameter) { + String name = getPropertyName(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 getPropertyName(VariableElement parameter) { + return getPropertyName(parameter, parameter.getSimpleName().toString()); + } + + private String getPropertyName(VariableElement parameter, String fallback) { AnnotationMirror nameAnnotation = this.environment.getNameAnnotation(parameter); if (nameAnnotation != null) { return this.environment.getAnnotationElementStringValue(nameAnnotation, "value"); } - return parameter.getSimpleName().toString(); + return fallback; } - 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(getPropertyName(field, 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(getPropertyName(field, name), propertyType, + declaringElement, getter, setter, field, factoryMethod)); }); return candidates.values().stream(); } @@ -126,20 +140,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 +161,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 +198,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, 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/fieldvalues/javac/ExpressionTree.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java index 9f4b5a23dea4..365e16be04d5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.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,12 @@ class ExpressionTree extends ReflectionWrapper { private final Method methodInvocationArgumentsMethod = findMethod(this.methodInvocationTreeType, "getArguments"); + private final Class memberSelectTreeType = findClass("com.sun.source.tree.MemberSelectTree"); + + private final Method memberSelectTreeExpressionMethod = findMethod(this.memberSelectTreeType, "getExpression"); + + private final Method memberSelectTreeIdentifierMethod = findMethod(this.memberSelectTreeType, "getIdentifier"); + private final Class newArrayTreeType = findClass("com.sun.source.tree.NewArrayTree"); private final Method arrayValueMethod = findMethod(this.newArrayTreeType, "getInitializers"); @@ -65,6 +71,17 @@ Object getFactoryValue() throws Exception { return null; } + Member getSelectedMember() throws Exception { + if (this.memberSelectTreeType.isAssignableFrom(getInstance().getClass())) { + String expression = this.memberSelectTreeExpressionMethod.invoke(getInstance()).toString(); + String identifier = this.memberSelectTreeIdentifierMethod.invoke(getInstance()).toString(); + if (expression != null && identifier != null) { + return new Member(expression, identifier); + } + } + return null; + } + List getArrayExpression() throws Exception { if (this.newArrayTreeType.isAssignableFrom(getInstance().getClass())) { List elements = (List) this.arrayValueMethod.invoke(getInstance()); @@ -80,4 +97,7 @@ List getArrayExpression() throws Exception { return null; } + record Member(String expression, String identifier) { + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java index c14bf74e73f4..d76fe5416ee5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -27,6 +28,8 @@ import javax.lang.model.element.TypeElement; import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser; +import org.springframework.boot.configurationprocessor.fieldvalues.javac.ExpressionTree.Member; +import org.springframework.boot.configurationprocessor.support.ConventionUtils; /** * {@link FieldValuesParser} implementation for the standard Java compiler. @@ -165,12 +168,12 @@ private Object getValue(VariableTree variable) throws Exception { Class wrapperType = WRAPPER_TYPES.get(variable.getType()); Object defaultValue = DEFAULT_TYPE_VALUES.get(wrapperType); if (initializer != null) { - return getValue(initializer, defaultValue); + return getValue(variable.getType(), initializer, defaultValue); } return defaultValue; } - private Object getValue(ExpressionTree expression, Object defaultValue) throws Exception { + private Object getValue(String variableType, ExpressionTree expression, Object defaultValue) throws Exception { Object literalValue = expression.getLiteralValue(); if (literalValue != null) { return literalValue; @@ -183,7 +186,7 @@ private Object getValue(ExpressionTree expression, Object defaultValue) throws E if (arrayValues != null) { Object[] result = new Object[arrayValues.size()]; for (int i = 0; i < arrayValues.size(); i++) { - Object value = getValue(arrayValues.get(i), null); + Object value = getValue(variableType, arrayValues.get(i), null); if (value == null) { // One of the elements could not be resolved return defaultValue; } @@ -195,7 +198,16 @@ private Object getValue(ExpressionTree expression, Object defaultValue) throws E return this.staticFinals.get(expression.toString()); } if (expression.getKind().equals("MEMBER_SELECT")) { - return WELL_KNOWN_STATIC_FINALS.get(expression.toString()); + Object value = WELL_KNOWN_STATIC_FINALS.get(expression.toString()); + if (value != null) { + return value; + } + Member selectedMember = expression.getSelectedMember(); + // Type matching the expression, assuming an enum + if (selectedMember != null && selectedMember.expression().equals(variableType)) { + return ConventionUtils.toDashedCase(selectedMember.identifier().toLowerCase(Locale.ENGLISH)); + } + return null; } return defaultValue; } 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 79c62bd3ed62..3b8c1fd2785a 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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,12 @@ package org.springframework.boot.configurationprocessor.metadata; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; + +import org.springframework.boot.configurationprocessor.support.ConventionUtils; /** * Configuration meta-data. @@ -36,13 +34,6 @@ */ public class ConfigurationMetadata { - private static final Set SEPARATORS; - - static { - List chars = Arrays.asList('-', '_'); - SEPARATORS = Collections.unmodifiableSet(new HashSet<>(chars)); - } - private final Map> items; private final Map> hints; @@ -184,31 +175,11 @@ 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; + String dashedName = ConventionUtils.toDashedCase(name); + nestedPrefix += nestedPrefix.isEmpty() ? dashedName : "." + dashedName; return nestedPrefix; } - static String toDashedCase(String name) { - StringBuilder dashed = new StringBuilder(); - Character previous = null; - for (int i = 0; i < name.length(); i++) { - char current = name.charAt(i); - if (SEPARATORS.contains(current)) { - dashed.append("-"); - } - else if (Character.isUpperCase(current) && previous != null && !SEPARATORS.contains(previous)) { - dashed.append("-").append(current); - } - else { - dashed.append(current); - } - previous = current; - - } - return dashed.toString().toLowerCase(Locale.ENGLISH); - } - private static > List flattenValues(Map> map) { List content = new ArrayList<>(); for (List values : map.values()) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java index 1b07489c1363..c44e22b89d4b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.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,8 @@ import java.util.List; import java.util.Map; +import org.springframework.boot.configurationprocessor.support.ConventionUtils; + /** * Provide hints on an {@link ItemMetadata}. Defines the list of possible values for a * particular item as {@link ItemHint.ValueHint} instances. @@ -53,9 +55,9 @@ private String toCanonicalName(String name) { if (dot != -1) { String prefix = name.substring(0, dot); String originalName = name.substring(dot); - return prefix + ConfigurationMetadata.toDashedCase(originalName); + return prefix + ConventionUtils.toDashedCase(originalName); } - return ConfigurationMetadata.toDashedCase(name); + return ConventionUtils.toDashedCase(name); } public String getName() { 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..33b71248595a 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. @@ -18,6 +18,8 @@ import java.util.Locale; +import org.springframework.boot.configurationprocessor.support.ConventionUtils; + /** * A group or property meta-data item from some {@link ConfigurationMetadata}. * @@ -68,7 +70,7 @@ private String buildName(String prefix, String name) { if (!fullName.isEmpty()) { fullName.append('.'); } - fullName.append(ConfigurationMetadata.toDashedCase(name)); + fullName.append(ConventionUtils.toDashedCase(name)); } return fullName.toString(); } @@ -196,7 +198,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); } @@ -218,7 +220,7 @@ public static ItemMetadata newProperty(String prefix, String name, String type, } public static String newItemMetadataPrefix(String prefix, String suffix) { - return prefix.toLowerCase(Locale.ENGLISH) + ConfigurationMetadata.toDashedCase(suffix); + return prefix.toLowerCase(Locale.ENGLISH) + ConventionUtils.toDashedCase(suffix); } /** 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 9f049bb72b23..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-2023 the original author or authors. + * Copyright 2012-2024 the original author 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,42 +66,53 @@ 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)); @@ -111,32 +123,35 @@ private ItemDeprecation toItemDeprecation(JSONObject object) throws Exception { 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")) { @@ -162,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/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.java new file mode 100644 index 000000000000..b9961e0bc70b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.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.configurationprocessor.support; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * Convention utilities. + * + * @author Stephane Nicoll + * @since 3.4.0 + */ +public abstract class ConventionUtils { + + private static final Set SEPARATORS; + + static { + List chars = Arrays.asList('-', '_'); + SEPARATORS = Collections.unmodifiableSet(new HashSet<>(chars)); + } + + /** + * Return the idiomatic metadata format for the given {@code value}. + * @param value a value + * @return the idiomatic format for the value, or the value itself if it already + * complies with the idiomatic metadata format. + */ + public static String toDashedCase(String value) { + StringBuilder dashed = new StringBuilder(); + Character previous = null; + for (int i = 0; i < value.length(); i++) { + char current = value.charAt(i); + if (SEPARATORS.contains(current)) { + dashed.append("-"); + } + else if (Character.isUpperCase(current) && previous != null && !SEPARATORS.contains(previous)) { + dashed.append("-").append(current); + } + else { + dashed.append(current); + } + previous = current; + + } + return dashed.toString().toLowerCase(Locale.ENGLISH); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/package-info.java new file mode 100644 index 000000000000..d94775642f7a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/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 classes for configuration metadata processing. + */ +package org.springframework.boot.configurationprocessor.support; 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 d1a831a95105..ac87c915d851 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. @@ -16,11 +16,18 @@ package org.springframework.boot.configurationprocessor; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; + import org.junit.jupiter.api.Test; 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,9 +49,11 @@ 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; +import org.springframework.boot.configurationsample.specific.EnumValuesPojo; import org.springframework.boot.configurationsample.specific.ExcludedTypesPojo; import org.springframework.boot.configurationsample.specific.InnerClassAnnotatedGetterConfig; import org.springframework.boot.configurationsample.specific.InnerClassHierarchicalProperties; @@ -168,6 +177,15 @@ void hierarchicalProperties() { .fromSource(HierarchicalProperties.class)); } + @Test + void enumValues() { + ConfigurationMetadata metadata = compile(EnumValuesPojo.class); + assertThat(metadata).has(Metadata.withGroup("test").fromSource(EnumValuesPojo.class)); + assertThat(metadata).has(Metadata.withProperty("test.seconds", ChronoUnit.class).withDefaultValue("seconds")); + assertThat(metadata) + .has(Metadata.withProperty("test.hour-of-day", ChronoField.class).withDefaultValue("hour-of-day")); + } + @Test void descriptionProperties() { ConfigurationMetadata metadata = compile(DescriptionProperties.class); @@ -333,6 +351,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 @@ -355,6 +377,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); @@ -508,4 +539,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..76c64f862742 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. @@ -17,26 +17,35 @@ package org.springframework.boot.configurationprocessor; import java.time.Duration; +import java.util.Locale; import org.junit.jupiter.api.Test; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.Metadata; +import org.springframework.boot.configurationsample.Access; import org.springframework.boot.configurationsample.endpoint.CamelCaseEndpoint; import org.springframework.boot.configurationsample.endpoint.CustomPropertiesEndpoint; import org.springframework.boot.configurationsample.endpoint.DisabledEndpoint; import org.springframework.boot.configurationsample.endpoint.EnabledEndpoint; +import org.springframework.boot.configurationsample.endpoint.NoAccessEndpoint; +import org.springframework.boot.configurationsample.endpoint.ReadOnlyAccessEndpoint; 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.UnrestrictedAccessEndpoint; 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 { @@ -45,16 +54,18 @@ void simpleEndpoint() { ConfigurationMetadata metadata = compile(SimpleEndpoint.class); assertThat(metadata).has(Metadata.withGroup("management.endpoint.simple").fromSource(SimpleEndpoint.class)); assertThat(metadata).has(enabledFlag("simple", true)); + assertThat(metadata).has(access("simple", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("simple")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); } @Test - void disableEndpoint() { + void disabledEndpoint() { ConfigurationMetadata metadata = compile(DisabledEndpoint.class); assertThat(metadata).has(Metadata.withGroup("management.endpoint.disabled").fromSource(DisabledEndpoint.class)); assertThat(metadata).has(enabledFlag("disabled", false)); - assertThat(metadata.getItems()).hasSize(2); + assertThat(metadata).has(access("disabled", Access.NONE)); + assertThat(metadata.getItems()).hasSize(3); } @Test @@ -62,7 +73,37 @@ void enabledEndpoint() { ConfigurationMetadata metadata = compile(EnabledEndpoint.class); assertThat(metadata).has(Metadata.withGroup("management.endpoint.enabled").fromSource(EnabledEndpoint.class)); assertThat(metadata).has(enabledFlag("enabled", true)); - assertThat(metadata.getItems()).hasSize(2); + assertThat(metadata).has(access("enabled", Access.UNRESTRICTED)); + assertThat(metadata.getItems()).hasSize(3); + } + + @Test + void noAccessEndpoint() { + ConfigurationMetadata metadata = compile(NoAccessEndpoint.class); + assertThat(metadata).has(Metadata.withGroup("management.endpoint.noaccess").fromSource(NoAccessEndpoint.class)); + assertThat(metadata).has(enabledFlag("noaccess", false)); + assertThat(metadata).has(access("noaccess", Access.NONE)); + assertThat(metadata.getItems()).hasSize(3); + } + + @Test + void readOnlyAccessEndpoint() { + ConfigurationMetadata metadata = compile(ReadOnlyAccessEndpoint.class); + assertThat(metadata) + .has(Metadata.withGroup("management.endpoint.readonlyaccess").fromSource(ReadOnlyAccessEndpoint.class)); + assertThat(metadata).has(enabledFlag("readonlyaccess", true)); + assertThat(metadata).has(access("readonlyaccess", Access.READ_ONLY)); + assertThat(metadata.getItems()).hasSize(3); + } + + @Test + void unrestrictedAccessEndpoint() { + ConfigurationMetadata metadata = compile(UnrestrictedAccessEndpoint.class); + assertThat(metadata).has(Metadata.withGroup("management.endpoint.unrestrictedaccess") + .fromSource(UnrestrictedAccessEndpoint.class)); + assertThat(metadata).has(enabledFlag("unrestrictedaccess", true)); + assertThat(metadata).has(access("unrestrictedaccess", Access.UNRESTRICTED)); + assertThat(metadata.getItems()).hasSize(3); } @Test @@ -74,8 +115,9 @@ void customPropertiesEndpoint() { .ofType(String.class) .withDefaultValue("test")); assertThat(metadata).has(enabledFlag("customprops", true)); + assertThat(metadata).has(access("customprops", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("customprops")); - assertThat(metadata.getItems()).hasSize(4); + assertThat(metadata.getItems()).hasSize(5); } @Test @@ -83,8 +125,9 @@ void specificEndpoint() { ConfigurationMetadata metadata = compile(SpecificEndpoint.class); assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class)); assertThat(metadata).has(enabledFlag("specific", true)); + assertThat(metadata).has(access("specific", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("specific")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); } @Test @@ -93,7 +136,8 @@ void camelCaseEndpoint() { assertThat(metadata) .has(Metadata.withGroup("management.endpoint.pascal-case").fromSource(CamelCaseEndpoint.class)); assertThat(metadata).has(enabledFlag("PascalCase", "pascal-case", true)); - assertThat(metadata.getItems()).hasSize(2); + assertThat(metadata).has(defaultAccess("PascalCase", "pascal-case", Access.UNRESTRICTED)); + assertThat(metadata.getItems()).hasSize(3); } @Test @@ -103,16 +147,18 @@ void incrementalEndpointBuildChangeGeneralEnabledFlag() { assertThat(metadata) .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); assertThat(metadata).has(enabledFlag("incremental", true)); + assertThat(metadata).has(access("incremental", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"", "id = \"incremental\", enableByDefault = false"); metadata = project.compile(); assertThat(metadata) .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); assertThat(metadata).has(enabledFlag("incremental", false)); + assertThat(metadata).has(access("incremental", Access.NONE)); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); } @Test @@ -122,14 +168,16 @@ void incrementalEndpointBuildChangeCacheFlag() { assertThat(metadata) .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); assertThat(metadata).has(enabledFlag("incremental", true)); + assertThat(metadata).has(access("incremental", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); project.replaceText(IncrementalEndpoint.class, "@Nullable String param", "String param"); metadata = project.compile(); assertThat(metadata) .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); assertThat(metadata).has(enabledFlag("incremental", true)); - assertThat(metadata.getItems()).hasSize(2); + assertThat(metadata).has(access("incremental", Access.UNRESTRICTED)); + assertThat(metadata.getItems()).hasSize(3); } @Test @@ -138,24 +186,57 @@ void incrementalEndpointBuildEnableSpecificEndpoint() { ConfigurationMetadata metadata = project.compile(); assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class)); assertThat(metadata).has(enabledFlag("specific", true)); + assertThat(metadata).has(access("specific", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("specific")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); project.replaceText(SpecificEndpoint.class, "enableByDefault = true", "enableByDefault = false"); metadata = project.compile(); assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class)); assertThat(metadata).has(enabledFlag("specific", false)); + assertThat(metadata).has(access("specific", Access.NONE)); assertThat(metadata).has(cacheTtl("specific")); - assertThat(metadata.getItems()).hasSize(3); + assertThat(metadata.getItems()).hasSize(4); + } + + @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(defaultAccess("simple", "simple", Access.UNRESTRICTED)); + assertThat(metadata).has(cacheTtl("simple")); + assertThat(metadata.getItems()).hasSize(4); + } + + @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, Boolean defaultValue) { + return enabledFlag(endpointId, endpointId, defaultValue); } private Metadata.MetadataItemCondition enabledFlag(String endpointId, String endpointSuffix, Boolean defaultValue) { return Metadata.withEnabledFlag("management.endpoint." + endpointSuffix + ".enabled") .withDefaultValue(defaultValue) - .withDescription(String.format("Whether to enable the %s endpoint.", endpointId)); + .withDescription(String.format("Whether to enable the %s endpoint.", endpointId)) + .withDeprecation(null, "management.endpoint.%s.access".formatted(endpointSuffix), "3.4.0"); } - private Metadata.MetadataItemCondition enabledFlag(String endpointId, Boolean defaultValue) { - return enabledFlag(endpointId, endpointId, defaultValue); + private Metadata.MetadataItemCondition access(String endpointId, Access defaultValue) { + return defaultAccess(endpointId, endpointId, defaultValue); + } + + private Metadata.MetadataItemCondition defaultAccess(String endpointId, String endpointSuffix, + Access defaultValue) { + return Metadata.withAccess("management.endpoint." + endpointSuffix + ".access") + .withDefaultValue(defaultValue.name().toLowerCase(Locale.ENGLISH)) + .withDescription("Permitted level of access for the %s endpoint.".formatted(endpointId)); } private Metadata.MetadataItemCondition cacheTtl(String endpointId) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutableNameAnnotationPropertiesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutableNameAnnotationPropertiesTests.java deleted file mode 100644 index 26a81530f451..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ImmutableNameAnnotationPropertiesTests.java +++ /dev/null @@ -1,41 +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.configurationprocessor; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; -import org.springframework.boot.configurationprocessor.metadata.Metadata; -import org.springframework.boot.configurationsample.immutable.ImmutableNameAnnotationProperties; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Metadata generation tests for immutable properties using {@code @Name}. - * - * @author Phillip Webb - */ -class ImmutableNameAnnotationPropertiesTests extends AbstractMetadataGenerationTests { - - @Test - void immutableNameAnnotationProperties() { - ConfigurationMetadata metadata = compile(ImmutableNameAnnotationProperties.class); - assertThat(metadata).has(Metadata.withProperty("named.import", String.class) - .fromSource(ImmutableNameAnnotationProperties.class)); - } - -} 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/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/NameAnnotationPropertiesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/NameAnnotationPropertiesTests.java new file mode 100644 index 000000000000..e43874cb4831 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/NameAnnotationPropertiesTests.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.configurationprocessor; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationprocessor.metadata.Metadata; +import org.springframework.boot.configurationsample.immutable.ConstructorParameterNameAnnotationProperties; +import org.springframework.boot.configurationsample.immutable.JavaBeanNameAnnotationProperties; +import org.springframework.boot.configurationsample.immutable.RecordComponentNameAnnotationProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Metadata generation tests for using {@code @Name}. + * + * @author Phillip Webb + */ +class NameAnnotationPropertiesTests extends AbstractMetadataGenerationTests { + + @Test + void constructorParameterNameAnnotationProperties() { + ConfigurationMetadata metadata = compile(ConstructorParameterNameAnnotationProperties.class); + assertThat(metadata).has(Metadata.withProperty("named.import", String.class) + .fromSource(ConstructorParameterNameAnnotationProperties.class)); + } + + @Test + void recordComponentNameAnnotationProperties() { + ConfigurationMetadata metadata = compile(RecordComponentNameAnnotationProperties.class); + assertThat(metadata).has(Metadata.withProperty("named.import", String.class) + .fromSource(RecordComponentNameAnnotationProperties.class)); + } + + @Test + void javaBeanNameAnnotationProperties() { + ConfigurationMetadata metadata = compile(JavaBeanNameAnnotationProperties.class); + assertThat(metadata).has( + Metadata.withProperty("named.import", String.class).fromSource(JavaBeanNameAnnotationProperties.class)); + } + +} 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 9a2b1aeed873..7fc50d0d56a3 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. @@ -31,11 +31,13 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor; +import org.springframework.boot.configurationsample.immutable.ConstructorParameterNameAnnotationProperties; import org.springframework.boot.configurationsample.immutable.ImmutableClassConstructorBindingProperties; import org.springframework.boot.configurationsample.immutable.ImmutableDeducedConstructorBindingProperties; import org.springframework.boot.configurationsample.immutable.ImmutableMultiConstructorProperties; -import org.springframework.boot.configurationsample.immutable.ImmutableNameAnnotationProperties; import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties; +import org.springframework.boot.configurationsample.immutable.JavaBeanNameAnnotationProperties; +import org.springframework.boot.configurationsample.immutable.RecordComponentNameAnnotationProperties; import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties; import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties; import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties; @@ -155,13 +157,25 @@ void propertiesWithMultiConstructorNoDirective() { } @Test - void propertiesWithNameAnnotationParameter() { - process(ImmutableNameAnnotationProperties.class, + void contructorParameterPropertyWithNameAnnotationParameter() { + process(ConstructorParameterNameAnnotationProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("import"))); + } + + @Test + void recordComponentPropertyWithNameAnnotationParameter() { + process(RecordComponentNameAnnotationProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("import"))); + } + + @Test + void javaBeanPropertyWithNameAnnotationParameter() { + process(JavaBeanNameAnnotationProperties.class, propertyNames((stream) -> assertThat(stream).containsExactly("import"))); } 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/fieldvalues/AbstractFieldValuesProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java index 138d7db7502d..812617d06efe 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java @@ -48,7 +48,7 @@ public abstract class AbstractFieldValuesProcessorTests { protected abstract FieldValuesParser createProcessor(ProcessingEnvironment env); @Test - void getFieldValues() throws Exception { + void getFieldValues() { TestProcessor processor = new TestProcessor(); TestCompiler compiler = TestCompiler.forSystem() .withProcessors(processor) @@ -105,6 +105,11 @@ void getFieldValues() throws Exception { assertThat(values.get("periodMonths")).isEqualTo("10m"); assertThat(values.get("periodYears")).isEqualTo("15y"); assertThat(values.get("periodZero")).isEqualTo(0); + assertThat(values.get("enumNone")).isNull(); + assertThat(values.get("enumSimple")).isEqualTo("seconds"); + assertThat(values.get("enumQualified")).isEqualTo("hour-of-day"); + assertThat(values.get("enumWithIndirection")).isNull(); + assertThat(values.get("memberSelectInt")).isNull(); } @SupportedAnnotationTypes({ "org.springframework.boot.configurationsample.ConfigurationProperties" }) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadataTests.java deleted file mode 100644 index 293008857217..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadataTests.java +++ /dev/null @@ -1,84 +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.configurationprocessor.metadata; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ConfigurationMetadata}. - * - * @author Stephane Nicoll - */ -class ConfigurationMetadataTests { - - @Test - void toDashedCaseCamelCase() { - assertThat(toDashedCase("simpleCamelCase")).isEqualTo("simple-camel-case"); - } - - @Test - void toDashedCaseUpperCamelCaseSuffix() { - assertThat(toDashedCase("myDLQ")).isEqualTo("my-d-l-q"); - } - - @Test - void toDashedCaseUpperCamelCaseMiddle() { - assertThat(toDashedCase("someDLQKey")).isEqualTo("some-d-l-q-key"); - } - - @Test - void toDashedCaseWordsUnderscore() { - assertThat(toDashedCase("Word_With_underscore")).isEqualTo("word-with-underscore"); - } - - @Test - void toDashedCaseWordsSeveralUnderscores() { - assertThat(toDashedCase("Word___With__underscore")).isEqualTo("word---with--underscore"); - } - - @Test - void toDashedCaseLowerCaseUnderscore() { - assertThat(toDashedCase("lower_underscore")).isEqualTo("lower-underscore"); - } - - @Test - void toDashedCaseUpperUnderscoreSuffix() { - assertThat(toDashedCase("my_DLQ")).isEqualTo("my-d-l-q"); - } - - @Test - void toDashedCaseUpperUnderscoreMiddle() { - assertThat(toDashedCase("some_DLQ_key")).isEqualTo("some-d-l-q-key"); - } - - @Test - void toDashedCaseMultipleUnderscores() { - assertThat(toDashedCase("super___crazy")).isEqualTo("super---crazy"); - } - - @Test - void toDashedCaseLowercase() { - assertThat(toDashedCase("lowercase")).isEqualTo("lowercase"); - } - - private String toDashedCase(String name) { - return ConfigurationMetadata.toDashedCase(name); - } - -} 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 f7054f721200..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", "1.2.3"))); + 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, @@ -66,6 +69,7 @@ void marshallAndUnmarshal() throws Exception { 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")); @@ -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 0930084cf4cc..effe586565c0 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. @@ -25,6 +25,7 @@ import org.hamcrest.collection.IsMapContaining; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType; +import org.springframework.boot.configurationsample.Access; import org.springframework.util.ObjectUtils; /** @@ -66,6 +67,10 @@ public static Metadata.MetadataItemCondition withEnabledFlag(String key) { return withProperty(key).ofType(Boolean.class); } + public static Metadata.MetadataItemCondition withAccess(String key) { + return withProperty(key).ofType(Access.class); + } + public static MetadataHintCondition withHint(String name) { return new MetadataHintCondition(name); } @@ -157,10 +162,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) { @@ -348,10 +350,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/configurationprocessor/support/ConventionUtilsTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/support/ConventionUtilsTests.java new file mode 100644 index 000000000000..6d1065dc1195 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/support/ConventionUtilsTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.support; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConventionUtils}. + * + * @author Stephane Nicoll + */ +class ConventionUtilsTests { + + @Test + void toDashedCaseCamelCase() { + assertThat(toDashedCase("simpleCamelCase")).isEqualTo("simple-camel-case"); + } + + @Test + void toDashedCaseUpperCamelCaseSuffix() { + assertThat(toDashedCase("myDLQ")).isEqualTo("my-d-l-q"); + } + + @Test + void toDashedCaseUpperCamelCaseMiddle() { + assertThat(toDashedCase("someDLQKey")).isEqualTo("some-d-l-q-key"); + } + + @Test + void toDashedCaseWordsUnderscore() { + assertThat(toDashedCase("Word_With_underscore")).isEqualTo("word-with-underscore"); + } + + @Test + void toDashedCaseWordsSeveralUnderscores() { + assertThat(toDashedCase("Word___With__underscore")).isEqualTo("word---with--underscore"); + } + + @Test + void toDashedCaseLowerCaseUnderscore() { + assertThat(toDashedCase("lower_underscore")).isEqualTo("lower-underscore"); + } + + @Test + void toDashedCaseUpperUnderscoreSuffix() { + assertThat(toDashedCase("my_DLQ")).isEqualTo("my-d-l-q"); + } + + @Test + void toDashedCaseUpperUnderscoreMiddle() { + assertThat(toDashedCase("some_DLQ_key")).isEqualTo("some-d-l-q-key"); + } + + @Test + void toDashedCaseMultipleUnderscores() { + assertThat(toDashedCase("super___crazy")).isEqualTo("super---crazy"); + } + + @Test + void toDashedCaseLowercase() { + assertThat(toDashedCase("lowercase")).isEqualTo("lowercase"); + } + + private String toDashedCase(String name) { + return ConventionUtils.toDashedCase(name); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java index cb0cbd48848f..ebb84efb5fe3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.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. @@ -74,6 +74,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name"; + public static final String ENDPOINT_ACCESS_ENUM = "org.springframework.boot.configurationsample.Access"; + public TestConfigurationMetadataAnnotationProcessor() { } @@ -123,4 +125,9 @@ protected String nameAnnotation() { return NAME_ANNOTATION; } + @Override + protected String endpointAccessEnum() { + return ENDPOINT_ACCESS_ENUM; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Access.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Access.java new file mode 100644 index 000000000000..8ed9bf2d50e6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Access.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.configurationsample; + +/** + * Permitted level of access to an endpoint. + * + * @author Andy Wilkinson + */ +public enum Access { + + NONE, + + READ_ONLY, + + UNRESTRICTED + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ControllerEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ControllerEndpoint.java index 5f055fbe02a6..5e5b0ad5dcc7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ControllerEndpoint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/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. @@ -35,6 +35,9 @@ String id() default ""; + @Deprecated boolean enableByDefault() default true; + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Endpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Endpoint.java index b28050d521c3..44cc2d9a0174 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Endpoint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Endpoint.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,9 @@ String id() default ""; + @Deprecated boolean enableByDefault() default true; + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/JmxEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/JmxEndpoint.java index a1e62e6321a1..eba43570eb72 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/JmxEndpoint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/JmxEndpoint.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. @@ -35,6 +35,9 @@ String id() default ""; + @Deprecated boolean enableByDefault() default true; + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Name.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Name.java index 965f8f4c0fb5..2d8969968dc6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Name.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/Name.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,7 +28,7 @@ * * @author Phillip Webb */ -@Target(ElementType.PARAMETER) +@Target({ ElementType.PARAMETER, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Name { 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/RestControllerEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/RestControllerEndpoint.java index 283b90e567c9..0643a67e1d23 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/RestControllerEndpoint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/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. @@ -35,6 +35,9 @@ String id() default ""; + @Deprecated boolean enableByDefault() default true; + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ServletEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ServletEndpoint.java index fecf8b08a30f..a3c0ccaf7eab 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ServletEndpoint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ServletEndpoint.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. @@ -35,6 +35,9 @@ String id() default ""; + @Deprecated boolean enableByDefault() default true; + Access defaultAccess() default Access.UNRESTRICTED; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/WebEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/WebEndpoint.java index 48b3ee40914f..f40415058f7f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/WebEndpoint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/WebEndpoint.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. @@ -35,6 +35,9 @@ String id() default ""; + @Deprecated boolean enableByDefault() default true; + Access defaultAccess() default Access.UNRESTRICTED; + } 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/NoAccessEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/NoAccessEndpoint.java new file mode 100644 index 000000000000..bbfa4a9768c7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/NoAccessEndpoint.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.configurationsample.endpoint; + +import org.springframework.boot.configurationsample.Access; +import org.springframework.boot.configurationsample.Endpoint; + +/** + * An endpoint with no permitted access unless configured explicitly. + * + * @author Andy Wilkinson + */ +@Endpoint(id = "noaccess", defaultAccess = Access.NONE) +public class NoAccessEndpoint { + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/ReadOnlyAccessEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/ReadOnlyAccessEndpoint.java new file mode 100644 index 000000000000..d3ec67d14f9e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/ReadOnlyAccessEndpoint.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.configurationsample.endpoint; + +import org.springframework.boot.configurationsample.Access; +import org.springframework.boot.configurationsample.Endpoint; + +/** + * An endpoint with read-only access unless configured explicitly. + * + * @author Andy Wilkinson + */ +@Endpoint(id = "readonlyaccess", defaultAccess = Access.READ_ONLY) +public class ReadOnlyAccessEndpoint { + +} 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/endpoint/UnrestrictedAccessEndpoint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/UnrestrictedAccessEndpoint.java new file mode 100644 index 000000000000..7267e1895726 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/UnrestrictedAccessEndpoint.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.configurationsample.endpoint; + +import org.springframework.boot.configurationsample.Endpoint; + +/** + * An endpoint with unrestricted access unless configured explicitly. + * + * @author Andy Wilkinson + */ +@Endpoint(id = "unrestrictedaccess") +public class UnrestrictedAccessEndpoint { + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java index ee8578c8a25f..84fc184fc7c8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.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.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Period; +import java.time.temporal.ChronoUnit; import org.springframework.boot.configurationsample.ConfigurationProperties; import org.springframework.util.MimeType; @@ -151,4 +152,22 @@ public class FieldValues { private Period periodZero = Period.ZERO; + private ChronoUnit enumNone; + + private ChronoUnit enumSimple = ChronoUnit.SECONDS; + + private java.time.temporal.ChronoField enumQualified = java.time.temporal.ChronoField.HOUR_OF_DAY; + + private ChronoUnit enumWithIndirection = SampleOptions.DEFAULT_UNIT; + + private int memberSelectInt = SampleOptions.DEFAULT_MAX_RETRIES; + + public static class SampleOptions { + + static final Integer DEFAULT_MAX_RETRIES = 20; + + static final ChronoUnit DEFAULT_UNIT = ChronoUnit.SECONDS; + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ConstructorParameterNameAnnotationProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ConstructorParameterNameAnnotationProperties.java new file mode 100644 index 000000000000..a482bc4c21e9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ConstructorParameterNameAnnotationProperties.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.configurationsample.immutable; + +import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.Name; + +/** + * Immutable class properties making use of {@code @Name}. + * + * @author Phillip Webb + */ +@ConfigurationProperties("named") +public class ConstructorParameterNameAnnotationProperties { + + private final String imports; + + public ConstructorParameterNameAnnotationProperties(@Name("import") String imports) { + this.imports = imports; + } + + public String getImports() { + return this.imports; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableNameAnnotationProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableNameAnnotationProperties.java deleted file mode 100644 index b8a2c8179732..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableNameAnnotationProperties.java +++ /dev/null @@ -1,40 +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; - -import org.springframework.boot.configurationsample.ConfigurationProperties; -import org.springframework.boot.configurationsample.Name; - -/** - * Immutable properties making use of {@code @Name}. - * - * @author Phillip Webb - */ -@ConfigurationProperties("named") -public class ImmutableNameAnnotationProperties { - - private final String imports; - - public ImmutableNameAnnotationProperties(@Name("import") String imports) { - this.imports = imports; - } - - public String getImports() { - return this.imports; - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/JavaBeanNameAnnotationProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/JavaBeanNameAnnotationProperties.java new file mode 100644 index 000000000000..a0e19ce9c18c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/JavaBeanNameAnnotationProperties.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.configurationsample.immutable; + +import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.Name; + +/** + * Java bean properties making use of {@code @Name}. + * + * @author Andy Wilkinson + */ +@ConfigurationProperties("named") +public class JavaBeanNameAnnotationProperties { + + @Name("import") + private String imports; + + public String getImports() { + return this.imports; + } + + public void setImports(String imports) { + this.imports = imports; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/RecordComponentNameAnnotationProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/RecordComponentNameAnnotationProperties.java new file mode 100644 index 000000000000..23b3764361b8 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/RecordComponentNameAnnotationProperties.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.immutable; + +import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.Name; + +/** + * Immutable record properties making use of {@code @Name}. + * + * @param imports some imports + * @author Andy Wilkinson + */ +@ConfigurationProperties("named") +public record RecordComponentNameAnnotationProperties(@Name("import") String imports) { + +} 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..7aea6ea0121c --- /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,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.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..9b5458cd4f87 --- /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,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.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/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/EnumValuesPojo.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/EnumValuesPojo.java new file mode 100644 index 000000000000..4b9542f215e9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/EnumValuesPojo.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Sample config for enum and default values. + * + * @author Stephane Nicoll + */ +@ConfigurationProperties("test") +public class EnumValuesPojo { + + private ChronoUnit seconds = ChronoUnit.SECONDS; + + private ChronoField hourOfDay = ChronoField.HOUR_OF_DAY; + + public ChronoUnit getSeconds() { + return this.seconds; + } + + public void setSeconds(ChronoUnit seconds) { + this.seconds = seconds; + } + + public ChronoField getHourOfDay() { + return this.hourOfDay; + } + + public void setHourOfDay(ChronoField hourOfDay) { + this.hourOfDay = hourOfDay; + } + +} 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 6408d4167ad5..4f0ff00c1f53 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 @@ -5,7 +5,7 @@ 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.docker-test" id "org.springframework.boot.maven-repository" id "org.springframework.boot.optional-dependencies" @@ -14,74 +14,14 @@ 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") } - modernGradleRuntimeClasspath { - extendsFrom runtimeClasspath - canBeConsumed = false - canBeResolved = true - } - modernGradleRuntimeElements { - extendsFrom configurations.implementation, configurations.runtimeOnly - canBeConsumed = true - canBeResolved = false - attributes { - attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category, Category.LIBRARY)) - attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling, Bundling.EXTERNAL)) - attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) - attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements, LibraryElements.JAR)) - attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, Usage.JAVA_RUNTIME)) - attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, project.objects.named(GradlePluginApiVersion, "8.7")) - } - outgoing.artifacts.addAll(configurations.runtimeElements.outgoing.artifacts) - } - runtimeElements { - attributes { - attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, project.objects.named(GradlePluginApiVersion, "7.5")) - } - } - all { configuration -> - if (configuration.name == 'modernGradleRuntimeClasspath') { - return - } - 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") - } - // Downgrade Spring Framework as Gradle cannot cope with 6.1.0-M1's - // multi-version jar files with bytecode in META-INF/versions/21 - if (dependency.requested.group.equals("org.springframework")) { - dependency.useVersion("$springFramework60xVersion") - } - // We manage the version of commons-compress here rather than - // in spring-boot-parent to minimize conflicts with Testcontainers - if (dependency.requested.group.equals("org.apache.commons") - && dependency.requested.name.equals("commons-compress")) { - dependency.useVersion("$commonsCompressVersion") - } - // Downgrade Testcontainers for compatibility with the managed - // version of Commons Compress. - if (dependency.requested.group.equals("org.testcontainers")) { - dependency.useVersion("1.19.3") - } - } - } - } -} - -components.java.addVariantsFromConfiguration(configurations.modernGradleRuntimeElements) { - mapToMavenScope("runtime") } 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()) @@ -93,10 +33,13 @@ dependencies { 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("io.spring.gradle:dependency-management-plugin") - implementation("org.apache.commons:commons-compress:$commonsCompressVersion") + implementation("org.apache.commons:commons-compress") 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") } @@ -120,6 +63,14 @@ dependencies { testImplementation("org.tomlj:tomlj:1.0.0") } +repositories { + gradlePluginPortal() { + content { + includeGroup("org.cyclonedx") + } + } +} + gradlePlugin { plugins { springBootPlugin { @@ -149,36 +100,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 { @@ -196,42 +119,35 @@ 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" } } -artifacts { - "documentation" zip +tasks.named("generateAntoraPlaybook") { + xrefStubs = ["appendix:.*", "api:.*", "reference:.*"] + excludeJavadocExtension = true + alwaysInclude = [name: "gradle-plugin", classifier: "local-aggregate-content"] + dependsOn antoraGradlePluginLocalAggregateContent } -toolchain { - maximumCompatibleJavaVersion = JavaLanguageVersion.of(20) +tasks.named("antora") { + inputs.files(antoraGradlePluginLocalAggregateContent, antoraGradlePluginCatalogContent) } -publishing { - publications.matching { it.name == 'pluginMaven' }.configureEach { - versionMapping { - allVariants { - fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME) - } - } - versionMapping { - variant(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, project.objects.named(GradlePluginApiVersion, "8.7")) { - fromResolutionOf("modernGradleRuntimeClasspath") - } - } - } +artifacts { + antoraContent antoraGradlePluginCatalogContent } plugins.withType(EclipsePlugin) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/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 index 219b150cc0d1..a8d01cf1865c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/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 @@ -43,6 +43,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi; 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.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.VolumeName; @@ -64,8 +65,6 @@ */ @GradleCompatibility(configurationCache = true) @DisabledIfDockerUnavailable -@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64", - disabledReason = "The builder image has no ARM support") class BootBuildImageIntegrationTests { GradleBuild gradleBuild; @@ -78,6 +77,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"); @@ -146,10 +162,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")); @@ -389,6 +406,57 @@ void buildsImageWithEmptySecurityOptions() throws IOException { removeImages(projectName); } + @TestTemplate + @EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "aarch64", + disabledReason = "Lifecycle will only run on ARM architecture") + void buildsImageOnLinuxArmWithImagePlatformLinuxArm() throws IOException { + writeMainClass(); + writeLongNameResource(); + String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1"; + String runImage = "docker.io/paketobuildpacks/run-jammy-tiny:latest"; + String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.1"; + removeImages(builderImage, runImage, buildpackImage); + 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("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()) + .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()).contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()) + .contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()).contains("Running detector"); + assertThat(result.getOutput()).contains("Running builder"); + assertThat(result.getOutput()).contains("---> Test Info buildpack building"); + assertThat(result.getOutput()).contains("---> Test Info buildpack done"); + removeImages(projectName, builderImage, runImage, buildpackImage); + } + + @TestTemplate + @EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "amd64", + disabledReason = "The expected failure condition will not fail on ARM architectures") + void failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm() throws IOException { + writeMainClass(); + writeLongNameResource(); + String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1"; + String runImage = "docker.io/paketobuildpacks/run-jammy-tiny:latest"; + String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.1"; + removeImages(builderImage, runImage, buildpackImage); + BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage"); + String projectName = this.gradleBuild.getProjectDir().getName(); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED); + assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()) + .contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()).contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()) + .contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'"); + assertThat(result.getOutput()).contains("exec format error"); + removeImages(builderImage, runImage, buildpackImage); + } + @TestTemplate void failsWithInvalidCreatedDate() throws IOException { writeMainClass(); @@ -571,7 +639,12 @@ private void writeCertificateBindingFiles() throws IOException { private void removeImages(String... names) throws IOException { ImageApi imageApi = new DockerApi().image(); for (String name : names) { - imageApi.remove(ImageReference.of(name), false); + try { + imageApi.remove(ImageReference.of(name), false); + } + catch (DockerEngineException ex) { + // ignore image remove failures + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/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 index 8d0e8dd00f10..397bb4dacddd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/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 @@ -75,7 +75,7 @@ void buildsImageAndPublishesToRegistry() throws IOException { .contains("Pushing image '" + imageName + ":latest" + "'") .contains("Pushed image '" + imageName + ":latest" + "'"); ImageReference imageReference = ImageReference.of(imageName); - Image pulledImage = new DockerApi().image().pull(imageReference, UpdateListener.none()); + Image pulledImage = new DockerApi().image().pull(imageReference, null, UpdateListener.none()); assertThat(pulledImage).isNotNull(); new DockerApi().image().remove(imageReference, false); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageOnLinuxArmWithImagePlatformLinuxArm.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageOnLinuxArmWithImagePlatformLinuxArm.gradle new file mode 100644 index 000000000000..5fa10d232dbc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageOnLinuxArmWithImagePlatformLinuxArm.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootBuildImage { + builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" + runImage = "paketobuildpacks/run-jammy-tiny" + buildpacks = ["ghcr.io/spring-io/spring-boot-test-info:0.0.1"] + imagePlatform = "linux/arm64" +} 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/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm.gradle new file mode 100644 index 000000000000..5fa10d232dbc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/dockerTest/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWhenBuildingOnLinuxAmdWithImagePlatformLinuxArm.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootBuildImage { + builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1" + runImage = "paketobuildpacks/run-jammy-tiny" + buildpacks = ["ghcr.io/spring-io/spring-boot-test-info:0.0.1"] + imagePlatform = "linux/arm64" +} 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/gradle/getting-started/apply-plugin-commercial.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-commercial.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-commercial.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-commercial.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-commercial.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-commercial.gradle.kts similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/getting-started/apply-plugin-commercial.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/getting-started/apply-plugin-commercial.gradle.kts 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/gradle/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 similarity index 75% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/configure-bom-with-plugins.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-with-plugins.gradle.kts index 5a6553150763..85988331381c 100644 --- 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/antora/modules/gradle-plugin/examples/managing-dependencies/configure-bom-with-plugins.gradle.kts @@ -3,8 +3,8 @@ 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}" + id("org.springframework.boot") version "{version-spring-boot}" apply false + id("io.spring.dependency-management") version "{version-dependency-management-plugin}" } dependencyManagement { 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 87% 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 06de18146bb2..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[] 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 89% 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 7eb5f2206956..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[] 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 88% 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 0cb4a011f92b..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[] 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 88% 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 3a94dc8adbd1..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[] 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 90% 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 bcd590932dd2..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 { 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 90% 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 69af1f2f2888..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 { 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 87% 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 6c512f68dfe0..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' 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 90% 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 1886a90dbd9d..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") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-commercial.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-commercial.gradle similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-commercial.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-commercial.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-commercial.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-commercial.gradle.kts similarity index 100% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/managing-dependencies/depend-on-plugin-commercial.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/managing-dependencies/depend-on-plugin-commercial.gradle.kts 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/gradle/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 similarity index 91% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-bind-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-bind-caches.gradle index 875239d07f80..34bf4bc0eade 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/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 @@ -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-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 similarity index 92% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-bind-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-bind-caches.gradle.kts index e492703c6f96..4b7dac528828 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/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 @@ -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-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 87% 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 daeafa87128f..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,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::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 88% 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 6f80be75c9b1..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,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::env-runtime[] 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/gradle/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 similarity index 85% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-env.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.gradle.kts index 0fd108ea19a7..06b6b6c498a5 100644 --- 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/antora/modules/gradle-plugin/examples/packaging/boot-build-image-env.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::env[] 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/gradle/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 similarity index 81% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle index 6a1897ae3d3b..73e6b0a7339c 100644 --- 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/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.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-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 similarity index 84% rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-war-properties-launcher.gradle.kts rename to spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.gradle.kts index f5284eb8f259..ae5e7fb7395a 100644 --- 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/antora/modules/gradle-plugin/examples/packaging/boot-war-properties-launcher.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/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..ee631e308f74 --- /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,52 @@ +[[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[] +---- +====== + + + +[[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..5e923ee0d1e2 --- /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,155 @@ +[[getting-started]] += Getting Started + +To get started with the plugin it needs to be applied to your project. + +ifeval::["{build-type}" == "commercial"] +The plugin is published to the Spring Commercial repository. +You will have to configure your build to access this repository. +This is usual done through a local artifact repository that mirrors the content of the Spring Commercial repository. +Alternatively, while it is not recommended, the Spring Commercial repository can also be accessed directly. +In either case, see https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/spring-enterprise-subscription.html[the Tanzu Spring Runtime documentation] for further details. + +With access to the Spring Commercial repository configured in `settings.gradle` or `settings.gradle.kts`, the plugin can be applied using the `plugins` block: + +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-commercial.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$getting-started/apply-plugin-commercial.gradle.kts[] +---- +====== +endif::[] + + +ifeval::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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..7b059d8cb139 --- /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.4 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..1955207a68bd --- /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,241 @@ +[[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::["{build-type}" == "commercial"] +[tabs] +====== +Groovy:: ++ +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-commercial.gradle[] +---- +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,attributes"] +---- +include::example$managing-dependencies/depend-on-plugin-commercial.gradle.kts[] +---- +====== +endif::[] + +ifeval::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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..e6fda41aa78f --- /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,684 @@ +[[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-java-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-java-tiny`, `paketobuildpacks/builder-noble-java-tiny`, `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. + +| `imagePlatform` +| `--imagePlatform` +a|The platform (operating system and architecture) of any builder, run, and buildpack images that are pulled. +Must be in the form of `OS[/architecture[/variant]]`, such as `linux/amd64`, `linux/arm64`, or `linux/arm/v5`. +Refer to documentation of the builder being used to determine the image OS and architecture options available. +| No default value, indicating that the platform of the host machine should be used. + +| `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` +| javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageReference#of-java.lang.String-[Image name] for the generated image. +| `docker.io/library/${project.name}:${project.version}` + +| `pullPolicy` +| `--pullPolicy` +| javadoc:org.springframework.boot.buildpack.platform.build.PullPolicy[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. + +| `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-java-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 following command: + +[source,shell,subs="verbatim,attributes"] +---- +$ 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..24e32f2534e1 --- /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,103 @@ +[[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 `bootJar` task to add the `Spring-Boot-Native-Processed: true` manifest entry. + + + +[[reacting-to-other-plugins.cyclonedx]] +== Reacting to the CycloneDX Plugin + +When the {url-cyclonedx-docs-gradle-plugin}[CycloneDX plugin] is applied to a project, the Spring Boot plugin: + +. Configures the `cyclonedxBom` task to use the `application` project type and output the SBOM to the `application.cdx` file in JSON format without full license texts. +. Adds the SBOM under `META-INF/sbom` in the generated jar or war file. +. Adds the `Sbom-Format` and `Sbom-Location` to the manifest of the jar or war file. 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 fa2e913c97c5..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/getting-started.adoc +++ /dev/null @@ -1,123 +0,0 @@ -[[getting-started]] -= Getting Started -To get started with the plugin it needs to be applied to your project. - -ifeval::["{build-type}" == "commercial"] -The plugin is published to the Spring Commercial repository. -You will have to configure your build to access this repository. -This is usual done through a local artifact repository that mirrors the content of the Spring Commercial repository. -Alternatively, while it is not recommended, the Spring Commercial repository can also be accessed directly. -In either case, see https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/spring-enterprise-subscription.html[the Tanzu Spring Runtime documentation] for further details. - -With access to the Spring Commercial repository configured in `settings.gradle` or `settings.gradle.kts`, the plugin can be applied using the `plugins` block: -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/getting-started/apply-plugin-commercial.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/getting-started/apply-plugin-commercial.gradle.kts[] ----- -endif::[] - -ifeval::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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 4232d13f7f73..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/integrating-with-actuator.adoc +++ /dev/null @@ -1,99 +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] ----- - -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/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 a7bf4a274df7..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/managing-dependencies.adoc +++ /dev/null @@ -1,196 +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::["{build-type}" == "commercial"] -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/managing-dependencies/depend-on-plugin-commercial.gradle[] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/managing-dependencies/depend-on-plugin-commercial.gradle.kts[] ----- -endif::[] - -ifeval::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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::["{build-and-artifact-release-type}" == "opensource-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 252b495bf770..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ /dev/null @@ -1,593 +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. -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 <>. - - - -[[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. -| - -| `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 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 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: - -[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] ----- - -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: - -[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] -.Groovy ----- -include::../gradle/packaging/boot-build-image-bind-caches.gradle[tags=caches] ----- - -[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] -.Kotlin ----- -include::../gradle/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: - -[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 31d1f2c1b32b..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc +++ /dev/null @@ -1,87 +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, 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 {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. Creats 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 {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/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-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/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/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/NativeImagePluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/NativeImagePluginAction.java index c4689c80b242..f46f62493af2 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,19 +22,16 @@ 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; -import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootJar; /** @@ -60,9 +57,7 @@ public void execute(Project project) { GraalVMExtension graalVmExtension = configureGraalVmExtension(project); configureMainNativeBinaryClasspath(project, sourceSets, graalVmExtension); configureTestNativeBinaryClasspath(sourceSets, graalVmExtension); - configureGraalVmReachabilityExtension(graalVmExtension); copyReachabilityMetadataToBootJar(project); - configureBootBuildImageToProduceANativeImage(project); configureJarManifestNativeAttribute(project); }); } @@ -99,27 +94,12 @@ 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) .configure((bootJar) -> bootJar.from(project.getTasks().named("collectReachabilityMetadata"))); } - 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"); - }); - } - private void configureJarManifestNativeAttribute(Project project) { project.getTasks() .named(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class) 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 5e949f1157de..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; @@ -115,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); } @@ -145,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/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 2da97a470c8d..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. @@ -39,6 +39,7 @@ * A Spring Boot "fat" archive task. * * @author Andy Wilkinson + * @author Moritz Halbritter * @since 2.0.0 */ public interface BootArchive extends Task { @@ -144,4 +145,13 @@ public interface BootArchive extends Task { @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 57da07239506..437547bf9435 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 @@ -128,7 +128,7 @@ CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, LoaderImplementation loaderImplementation, boolean supportsSignatureFile, LayerResolver layerResolver, - String layerToolsLocation) { + String jarmodeToolsLocation) { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); @@ -142,7 +142,7 @@ CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, Function compressionResolver = this.compressionResolver; String encoding = jar.getMetadataCharset(); CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions, - filePermissions, includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, + filePermissions, 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 f0a2751b887e..4182e0c23c2b 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 @@ -91,6 +91,7 @@ public BootBuildImage() { } return ImageReference.of(imageName, projectVersion.get()).toString(); })); + getTrustBuilder().convention((Boolean) null); getCleanCache().convention(false); getVerboseLogging().convention(false); getPublish().convention(false); @@ -131,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. @@ -315,6 +326,18 @@ public void launchCache(Action action) { @Option(option = "securityOptions", description = "Security options that will be applied to the builder container") public abstract ListProperty getSecurityOptions(); + /** + * Returns the platform (os/architecture/variant) that will be used for all pulled + * images. When {@code null}, the system will choose a platform based on the host + * operating system and architecture. + * @return the image platform + */ + @Input + @Optional + @Option(option = "imagePlatform", + description = "The platform (os/architecture/variant) that will be used for all pulled images") + public abstract Property getImagePlatform(); + /** * Returns the Docker configuration the builder will use. * @return docker configuration. @@ -348,13 +371,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); @@ -363,6 +389,9 @@ private BuildRequest customize(BuildRequest request) { request = customizeCreatedDate(request); request = customizeApplicationDirectory(request); request = customizeSecurityOptions(request); + if (getImagePlatform().isPresent()) { + request = request.withImagePlatform(getImagePlatform().get()); + } return request; } @@ -406,11 +435,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)) { 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 7ed3f998c54f..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. @@ -88,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) { @@ -144,13 +145,19 @@ 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, loaderImplementation, true, - layerResolver, layerToolsLocation); + layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, true); + 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 d19f152f84b6..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. @@ -87,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() { @@ -118,13 +119,19 @@ 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, loaderImplementation, false, - layerResolver, layerToolsLocation); + layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, false); + 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 ce8cf3616090..fdde482b7e2c 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 @@ -95,7 +95,7 @@ class BootZipCopyAction implements CopyAction { private final boolean includeDefaultLoader; - private final String layerToolsLocation; + private final String jarmodeToolsLocation; private final Spec requiresUnpack; @@ -118,7 +118,7 @@ class BootZipCopyAction implements CopyAction { 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, boolean supportsSignatureFile, LayerResolver layerResolver, @@ -129,7 +129,7 @@ class BootZipCopyAction implements CopyAction { this.dirMode = dirMode; this.fileMode = fileMode; this.includeDefaultLoader = includeDefaultLoader; - this.layerToolsLocation = layerToolsLocation; + this.jarmodeToolsLocation = jarmodeToolsLocation; this.requiresUnpack = requiresUnpack; this.exclusions = exclusions; this.launchScript = launchScript; @@ -341,8 +341,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); } } 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/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 d9cfc3d49226..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,7 +160,7 @@ 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(); @@ -172,7 +172,7 @@ void bootWarPropertiesLauncher() throws IOException { @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,7 +341,7 @@ 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"); @@ -349,7 +349,7 @@ void bootBuildImageWithCaches() { @TestTemplate void bootBuildImageWithBindCaches() { - BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-bind-caches") + 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") 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/NativeImagePluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java index c711b77e58e4..cf9d67951c27 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 @@ -88,14 +88,6 @@ void reachabilityMetadataConfigurationFilesFromFileRepositoryAreCopiedToJar() th "META-INF/native-image/org.jline/jline/3.21.0/resource-config.json"); } - @TestTemplate - void bootBuildImageIsConfiguredToBuildANativeImage() { - writeDummySpringApplicationAotProcessorMainClass(); - BuildResult result = this.gradleBuild.build("bootBuildImageConfiguration"); - assertThat(result.getOutput()).contains("paketobuildpacks/builder-jammy-tiny") - .contains("BP_NATIVE_IMAGE = true"); - } - @TestTemplate void developmentOnlyDependenciesDoNotAppearInNativeImageClasspath() { writeDummySpringApplicationAotProcessorMainClass(); 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 6fe265491757..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,49 +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.gradle.testkit.PluginClasspathGradleBuild; -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 PluginClasspathGradleBuild(); - - @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 c3cc0a5808de..df29ef1ef0f9 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 @@ -66,6 +66,7 @@ * @author Andy Wilkinson * @author Madhura Bhave * @author Scott Frederick + * @author Moritz Halbritter */ abstract class AbstractBootArchiveIntegrationTests { @@ -332,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()) @@ -345,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(); @@ -397,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(); @@ -443,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(); @@ -490,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(); 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 c5c78eb5a21c..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. @@ -80,6 +80,7 @@ * @param the type of the concrete BootArchive implementation * @author Andy Wilkinson * @author Scott Frederick + * @author Moritz Halbritter */ abstract class AbstractBootArchiveTests { @@ -496,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()); } } @@ -530,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\""); @@ -584,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 + "\""); @@ -614,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).isNotEmpty() - .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 c5ef89486d6d..6eeaf59c8151 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 @@ -31,6 +31,7 @@ import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.type.Binding; +import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.gradle.junit.GradleProjectBuilder; @@ -172,14 +173,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-java-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 @@ -314,4 +325,15 @@ void whenSecurityOptionsAreConfiguredThenRequestHasSecurityOptions() { "label=role:ROLE"); } + @Test + void whenImagePlatformIsNotConfiguredThenRequestHasNoImagePlatform() { + assertThat(this.buildImage.createRequest().getImagePlatform()).isNull(); + } + + @Test + void whenImagePlatformIsConfiguredThenRequestHasImagePlatform() { + this.buildImage.getImagePlatform().set("linux/arm64/v1"); + assertThat(this.buildImage.createRequest().getImagePlatform()).isEqualTo(ImagePlatform.of("linux/arm64/v1")); + } + } 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 d83e54ed165c..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. @@ -67,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. "); } @@ -77,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/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 deleted file mode 100644 index 5af90e228e91..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests-bootBuildImageIsConfiguredToBuildANativeImage.gradle +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id 'org.springframework.boot' version '{version}' - id 'java' -} - -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/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-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-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 index 7f4ca313065c..27347023a828 100644 --- 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 @@ -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-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 index 45041d1c1908..d0dfd4e27f8e 100644 --- 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 @@ -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/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 index f2d285e40810..00efac247c1a 100644 --- 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 @@ -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-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 index de8e9d652170..5688972529c3 100644 --- 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 @@ -21,7 +21,5 @@ bootWar { } bootWar { - layered { - enabled = false - } + includeTools = false } 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 17998b002806..38f9844ec757 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 @@ -42,7 +42,7 @@ public static List allCompatible() { if (isJavaVersion(JavaVersion.VERSION_21)) { return Arrays.asList("8.5", GradleVersion.current().getVersion()); } - return Arrays.asList("7.6.4", "8.3", GradleVersion.current().getVersion()); + return Arrays.asList("7.6.4", "8.4", GradleVersion.current().getVersion()); } 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 5680a37b70ac..000000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id "java-library" - id "org.springframework.boot.deployed" -} - -description = "Spring Boot Layers 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/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 4f25c80dc259..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,104 +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.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, parameters); - } - - void run(PrintStream out, 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.printf(" --%-" + padding + "s %s%n", 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(" ").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.printf(" %-" + padding + "s %s%n", 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 9acb9c9c6ca8..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-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF 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.emptyList()); - assertThat(this.out).hasSameContentAsResource("help-output.txt"); - } - - @Test - void runWhenHasNoCommandParameterPrintsUsage() { - this.command.run(this.out, 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..ab2896e4afc1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/build.gradle @@ -0,0 +1,21 @@ +plugins { + id "java-library" + id "org.springframework.boot.deployed" +} + +description = "Spring Boot Jarmode 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/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