diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 26c1be09451..00000000000 --- a/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root=true - -[*.java] -indent_style = tab -indent_size = 4 -continuation_indent_size = 8 - -[*.xml] -indent_style = tab -indent_size = 4 -continuation_indent_size = 8 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index ce36d5cc42b..00000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: 'type: bug, status: waiting-for-triage' -assignees: '' - ---- - -**In what version(s) of Spring Integration are you seeing this issue?** - -For example: - -5.3.2.RELEASE - -Between 4.3.15 and 5.2.5 - -**Describe the bug** - -A clear and concise description of what the bug is. - -**To Reproduce** - -Steps to reproduce the behavior. - -**Expected behavior** - -A clear and concise description of what you expected to happen. - -**Sample** - -A link to a GitHub repository with a [minimal, reproducible sample](https://stackoverflow.com/help/minimal-reproducible-example). - -Reports that include a sample will take priority over reports that do not. -At times, we may require a sample, so it is good to try and include a sample up front. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index dee93ce521f..00000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Community Support - url: https://stackoverflow.com/questions/tagged/spring-integration - about: Please ask and answer questions on StackOverflow with the tag spring-integration diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 381a8977f98..00000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: 'status: waiting-for-triage, type: enhancement' -assignees: '' - ---- - -**Expected Behavior** - - - -**Current Behavior** - - - -**Context** - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index f46ea1167a8..00000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/.github/dco.yml b/.github/dco.yml deleted file mode 100644 index 0c4b142e9a7..00000000000 --- a/.github/dco.yml +++ /dev/null @@ -1,2 +0,0 @@ -require: - members: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 72b4cecc85f..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,172 +0,0 @@ -version: 2 -updates: - - package-ecosystem: gradle - directory: / - schedule: - interval: weekly - day: sunday - ignore: - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - open-pull-requests-limit: 10 - labels: - - 'type: dependency-upgrade' - groups: - development-dependencies: - update-types: - - patch - patterns: - - org.gradle.* - - io.spring.* - - org.antora - - com.google.protobuf - - io.micrometer:micrometer-docs-generator - - com.willowtreeapps.assertk:assertk-jvm - - org.jetbrains.dokka - - org.apache.activemq* - - org.aspectj* - - org.awaitility:awaitility - - org.apache.commons:commons-dbcp2 - - org.apache.derby - - com.icegreen:greenmail - - org.hibernate.orm* - - org.testcontainers* - - org.hsqldb:hsqldb - - com.h2database:h2 - - org.postgresql:postgresql - - mysql:mysql-connector-java - - com.oracle.database.jdbc:ojdbc11 - - org.apache.tomcat.embed:tomcat-embed-websocket - - org.xmlunit:xmlunit-assertj3 - - com.thoughtworks.xstream:xstream - - org.springframework.security* - - - package-ecosystem: gradle - target-branch: 6.5.x - directory: / - schedule: - interval: weekly - day: sunday - ignore: - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - open-pull-requests-limit: 10 - labels: - - 'type: dependency-upgrade' - groups: - development-dependencies: - update-types: - - patch - patterns: - - org.gradle.* - - com.github.spotbugs - - io.spring.* - - org.antora - - com.google.protobuf - - io.micrometer:micrometer-docs-generator - - com.willowtreeapps.assertk:assertk-jvm - - org.jetbrains.dokka - - org.apache.activemq* - - org.aspectj* - - org.awaitility:awaitility - - org.apache.commons:commons-dbcp2 - - org.apache.derby - - com.icegreen:greenmail - - org.hibernate.orm* - - org.testcontainers* - - org.hsqldb:hsqldb - - com.h2database:h2 - - org.postgresql:postgresql - - mysql:mysql-connector-java - - com.oracle.database.jdbc:ojdbc11 - - org.apache.tomcat.embed:tomcat-embed-websocket - - org.xmlunit:xmlunit-assertj3 - - com.thoughtworks.xstream:xstream - - org.springframework.security* - - - package-ecosystem: gradle - target-branch: 6.4.x - directory: / - schedule: - interval: weekly - day: sunday - ignore: - - dependency-name: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - open-pull-requests-limit: 10 - labels: - - 'type: dependency-upgrade' - groups: - development-dependencies: - update-types: - - patch - patterns: - - org.gradle.* - - com.github.spotbugs - - io.spring.* - - org.antora - - com.google.protobuf - - io.micrometer:micrometer-docs-generator - - com.willowtreeapps.assertk:assertk-jvm - - org.jetbrains.dokka - - org.apache.activemq* - - org.aspectj* - - org.awaitility:awaitility - - org.apache.commons:commons-dbcp2 - - org.apache.derby - - com.icegreen:greenmail - - org.hibernate.orm* - - org.testcontainers* - - org.hsqldb:hsqldb - - com.h2database:h2 - - org.postgresql:postgresql - - mysql:mysql-connector-java - - com.oracle.database.jdbc:ojdbc11 - - org.apache.tomcat.embed:tomcat-embed-websocket - - org.xmlunit:xmlunit-assertj3 - - com.thoughtworks.xstream:xstream - - org.springframework.security* - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: sunday - labels: - - 'type: task' - groups: - development-dependencies: - patterns: - - '*' - - - package-ecosystem: github-actions - target-branch: 6.5.x - directory: / - schedule: - interval: weekly - day: sunday - labels: - - 'type: task' - groups: - development-dependencies: - patterns: - - '*' - - - package-ecosystem: github-actions - target-branch: 6.4.x - directory: / - schedule: - interval: weekly - day: sunday - labels: - - 'type: task' - groups: - development-dependencies: - patterns: - - '*' diff --git a/.github/workflows/announce-milestone-planning.yml b/.github/workflows/announce-milestone-planning.yml deleted file mode 100644 index f05fb423377..00000000000 --- a/.github/workflows/announce-milestone-planning.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Announce Milestone Planning in Chat - -on: - milestone: - types: [ created, edited ] - -jobs: - announce-milestone-planning: - uses: spring-io/spring-github-workflows/.github/workflows/spring-announce-milestone-planning.yml@v5 - secrets: - SPRING_RELEASE_CHAT_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_GCHAT_WEBHOOK_URL }} diff --git a/.github/workflows/auto-cherry-pick.yml b/.github/workflows/auto-cherry-pick.yml deleted file mode 100644 index 6ba14dde29d..00000000000 --- a/.github/workflows/auto-cherry-pick.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Auto Cherry-Pick - -on: - push: - branches: - - main - - '*.x' - -jobs: - cherry-pick-commit: - uses: spring-io/spring-github-workflows/.github/workflows/spring-cherry-pick.yml@v5 - secrets: - GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/backport-issue.yml b/.github/workflows/backport-issue.yml deleted file mode 100644 index ae3ea05130e..00000000000 --- a/.github/workflows/backport-issue.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Backport Issue - -on: - push: - branches: - - '*.x' - -jobs: - backport-issue: - uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@main - secrets: - GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ci-snapshot.yml b/.github/workflows/ci-snapshot.yml deleted file mode 100644 index 5dc0329c879..00000000000 --- a/.github/workflows/ci-snapshot.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: CI SNAPSHOT - -on: - workflow_dispatch: - inputs: - gradleOptions: - description: 'The Gradle CLI options: tasks, properties etc.' - required: false - type: string - - push: - branches: - - main - - '*.x' - - schedule: - - cron: '0 5 * * *' - -concurrency: - group: group-snapshot-for-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-snapshot: - uses: spring-io/spring-github-workflows/.github/workflows/spring-artifactory-gradle-snapshot.yml@main - with: - gradleTasks: ${{ github.event_name == 'schedule' && '--rerun-tasks --refresh-dependencies' || inputs.gradleOptions }} - secrets: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 2065ee71873..3a4ff2dda0d 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,19 +1,25 @@ name: Deploy Docs - +run-name: ${{ format('{0} ({1})', github.workflow, github.event.inputs.build-refname || 'all') }} on: - push: - branches: - - '*.x' - - main - tags: - - '**' - workflow_dispatch: - -permissions: - actions: write + inputs: + build-refname: + description: Enter git refname to build (e.g., 5.7.x). + required: false + push: + branches: docs-build jobs: - dispatch-docs-build: + build: if: github.repository_owner == 'spring-projects' - uses: spring-io/spring-github-workflows/.github/workflows/spring-dispatch-docs-build.yml@v5 + uses: spring-io/spring-github-workflows/.github/workflows/spring-build-and-deploy-docs.yml@main + with: + build-refname: ${{ inputs.build-refname }} + secrets: + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + DOCS_HOST: ${{ secrets.DOCS_HOST }} + DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} + DOCS_SSH_HOST_KEY: ${{ secrets.DOCS_SSH_HOST_KEY }} + CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} + CLOUDFLARE_CACHE_TOKEN: ${{ secrets.CLOUDFLARE_CACHE_TOKEN }} diff --git a/.github/workflows/manual-finalize-release.yml b/.github/workflows/manual-finalize-release.yml deleted file mode 100644 index bd0405f924d..00000000000 --- a/.github/workflows/manual-finalize-release.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Finalize Release Manually - -on: - workflow_dispatch: - inputs: - milestone: - description: 'Milestone title, e.g 3.0.0-M1, 3.1.0-RC1, 3.2.0 etc.' - -jobs: - finalize-release: - permissions: - actions: write - contents: write - issues: write - - uses: spring-io/spring-github-workflows/.github/workflows/spring-finalize-release.yml@main - with: - milestone: ${{ inputs.milestone }} - secrets: - GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - SPRING_RELEASE_CHAT_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_GCHAT_WEBHOOK_URL }} \ No newline at end of file diff --git a/.github/workflows/merge-dependabot-pr.yml b/.github/workflows/merge-dependabot-pr.yml deleted file mode 100644 index 0b1d927d8ea..00000000000 --- a/.github/workflows/merge-dependabot-pr.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Merge Dependabot PR - -on: - pull_request: - branches: - - main - - '*.x' - -run-name: Merge Dependabot PR ${{ github.ref_name }} - -jobs: - merge-dependabot-pr: - permissions: write-all - - uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@main - with: - mergeArguments: --auto --squash - autoMergeSnapshots: true \ No newline at end of file diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml deleted file mode 100644 index cefa6bf31a9..00000000000 --- a/.github/workflows/pr-build.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Pull Request Build - -on: - pull_request: - branches: - - main - - '*.x' - -jobs: - build-pull-request: - uses: spring-io/spring-github-workflows/.github/workflows/spring-gradle-pull-request-build.yml@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 518c1b03926..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Release - -on: - workflow_dispatch: - -run-name: Release current version for branch ${{ github.ref_name }} - -jobs: - release: - permissions: - actions: write - contents: write - issues: write - - uses: spring-io/spring-github-workflows/.github/workflows/spring-artifactory-gradle-release.yml@main - with: - deployMilestoneToCentral: true - secrets: - GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - JF_ARTIFACTORY_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} - CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - SPRING_RELEASE_CHAT_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_GCHAT_WEBHOOK_URL }} \ No newline at end of file diff --git a/.github/workflows/verify-staged-artifacts.yml b/.github/workflows/verify-staged-artifacts.yml deleted file mode 100644 index cc5b90d1f55..00000000000 --- a/.github/workflows/verify-staged-artifacts.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Verify Staged Artifacts - -on: - workflow_dispatch: - inputs: - releaseVersion: - description: 'Release version like 5.0.0-M1, 5.1.0-RC1, 5.2.0 etc.' - required: true - type: string - -env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - -jobs: - verify-staged-with-samples: - runs-on: ubuntu-latest - steps: - - - name: Checkout Samples Repo - uses: actions/checkout@v5 - with: - repository: spring-projects/spring-integration-samples - ref: ${{ github.ref_name }} - show-progress: false - - - name: Set up Gradle - uses: spring-io/spring-gradle-build-action@v2 - - - name: Prepare Samples project against Staging - run: | - printf "allprojects { - repositories { - maven { - url 'https://repo.spring.io/libs-staging-local' - credentials { - username = '$ARTIFACTORY_USERNAME' - password = '$ARTIFACTORY_PASSWORD' - } - } - } - }" > staging-repo-init.gradle - - sed -i "1,/springIntegrationVersion.*/s/springIntegrationVersion.*/springIntegrationVersion='${{ inputs.releaseVersion }}'/" build.gradle - - - name: Verify Spring Integration Samples against staged release - run: ./gradlew check --init-script staging-repo-init.gradle diff --git a/.gitignore b/.gitignore index cf03e8a97cd..ec87d9f6dbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,11 @@ -*.iml -*.ipr -*.iws -*.msg -*.sw? -*/src/main/java/META-INF -.classpath -.DS_Store -.gradle -.idea -.pmd -.project -.settings -.checkstyle -bin -build -build.log -derby.log -lib -logs -nohup.out -out -si.java.hsp -spring-integration-file/test/fileToAppend.txt -spring-integration-file/test/fileToAppendConcurrent.txt -spring-integration-jms/activemq-data/ -spring-integration-samples/loanshark/application.log* -target -vf.gf.dmn-* -/atlassian-ide-plugin.xml -hostkey.ser -.springBeans -.sts4-cache -.vscode/ +/.gradle/ +/.idea/* +/.settings/ +/.classpath +/.project +/build/ +/node_modules/ +/package-lock.json +/*.iml +/*.ipr +/*.iws diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc deleted file mode 100644 index ddd36a5f2ef..00000000000 --- a/CONTRIBUTING.adoc +++ /dev/null @@ -1,267 +0,0 @@ -= Spring Integration Contributor Guidelines - -Have something you'd like to contribute to **Spring Integration**? -We welcome pull requests, but ask that you carefully read this document first to understand how best to submit them; what kind of changes are likely to be accepted; and what to expect from the Spring team when evaluating your submission. - -Please refer back to this document as a checklist before issuing any pull request; this will save time for everyone! - -== Code of Conduct - -Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct] - -== Reporting Security Vulnerabilities - -Please see our https://github.com/spring-projects/spring-integration/security/policy[Security policy]. - -== Understand the basics - -Not sure what a *pull request* is, or how to submit one? -Take a look at GitHub's excellent documentation: https://help.github.com/articles/using-pull-requests/[Using Pull Requests] first. - -== Search GitHub issues first; create one if necessary - -Is there already an issue that addresses your concern? -Search the https://github.com/spring-projects/spring-integration/issues[GitHub issue tracker] to see if you can find something similar. -If not, please create a new issue in GitHub before submitting a pull request unless the change is truly trivial, e.g. typo fixes, removing compiler warnings, etc. - -== Developer Certificate of Origin - -All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. -For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. - -== Fork the Repository - -1. Go to https://github.com/spring-projects/spring-integration[https://github.com/spring-projects/spring-integration] -2. Hit the "fork" button and choose your own GitHub account as the target -3. For more detail see https://help.github.com/articles/fork-a-repo/[Fork A Repo]. - -== Setup your Local Development Environment - -1. `git clone --recursive git@github.com:/spring-integration.git` -2. `cd spring-integration` -3. `git remote show` -_you should see only 'origin' - which is the fork you created for your own GitHub account_ -4. `git remote add upstream git@github.com:spring-projects/spring-integration.git` -5. `git remote show` -_you should now see 'upstream' in addition to 'origin' where 'upstream' is the Spring repository from which releases are built_ -6. `git fetch --all` -7. `git branch -a` -_you should see branches on origin as well as upstream, including 'main'_ - -== Build from Source - -The build system for the project is https://gradle.org/[Gradle]. -It is recommended to rely on the `wrapper` provided in the project code based and use a `gradlew` script from command line for the target operating system. -The current Gradle version in use you can obtain from the `/gradle/gradle-wrapper.properties` file in the source tree. -It is also recommended to use a Gradle import feature of your IDE for the best contribution experience. - -The minimum JDK version at the moment is `17`. -You always can find the currently required Java version in the `build.gradle`. -For example, for the current project version: - ----- -compileJava { - options.release = 17 -} - -compileTestJava { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - options.encoding = 'UTF-8' -} ----- - -To build and install jars into your local Maven cache: - ----- -./gradlew build publishToMavenLocal ----- - -To build api Javadoc (results will be in `build/api`): - ----- -./gradlew api ----- - -To build the reference documentation (results will be in `build/site`): - ----- -./gradlew antora ----- - -To build complete distribution including `-dist`, `-docs`, and `-schema` zip files (results will be in `build/distributions`): - ----- -./gradlew dist ----- - -You can build and test only a specific module if your contribution is only over there: - ----- -./gradlew :spring-integration-webflux:test ----- - -== A Day in the Life of a Contributor - -* _Always_ work on topic branches (Typically use the GitHub issue ID as the branch name). - - For example, to create and switch to a new branch for issue 123: `git checkout -b GH-123` -* You might be working on several different topic branches at any given time, but when at a stopping point for one of those branches, commit (a local operation). -* Please follow the "Commit Guidelines" described in https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project[this chapter of Pro Git]. -* Then to begin working on another issue (say 101): `git checkout GH-101`. - The _-b_ flag is not needed if that branch already exists in your local repository. -* When ready to resolve an issue or to collaborate with others, you can push your branch to origin (your fork), e.g.: `git push origin GH-123` -* If you want to collaborate with another contributor, have them fork your repository (add it as a remote) and `git fetch ` to grab your branch. -Alternatively, they can use `git fetch --all` to sync their local state with all of their remotes. -* If you grant that collaborator push access to your repository, they can even apply their changes to your branch. -* When ready for your contribution to be reviewed for potential inclusion in the main branch of the canonical `spring-integration` repository (what you know as 'upstream'), issue a pull request to the SpringSource repository (for more detail, see https://help.github.com/articles/using-pull-requests/[Using pull requests]). -* The project lead may merge your changes into the upstream main branch as-is, he may keep the pull request open yet add a comment about something that should be modified, or he might reject the pull request by closing it. -* A prerequisite for any pull request is that it will be cleanly merge-able with the upstream main's current state. -**This is the responsibility of any contributor.** -If your pull request cannot be applied cleanly, the project lead will most likely add a comment requesting that you make it merge-able. -For a full explanation, see https://git-scm.com/book/en/Git-Branching-Rebasing[the Pro Git section on rebasing]. -As stated there: _"> Often, you’ll do this to make sure your commits apply cleanly on a remote branch — perhaps in a project to which you’re trying to contribute but that you don’t maintain."_ - -== Keeping your Local Code in Sync - -* As mentioned above, you should always work on topic branches (since 'main' is a moving target). -However, you do want to always keep your own 'origin' main branch in synch with the 'upstream' main. -* Within your local working directory, you can sync up all remotes' branches with: `git fetch --all` -* While on your own local main branch: `git pull upstream main` (which is the equivalent of fetching upstream/main and merging that into the branch you are in currently) -* Now that you're in synch, switch to the topic branch where you plan to work, e.g.: `git checkout -b GH-123` -* When you get to a stopping point: `git commit` -* If changes have occurred on the upstream/main while you were working you can sync again: - - Switch back to main: `git checkout main` - - Then: `git pull upstream main` - - Switch back to the topic branch: `git checkout GH-123` (no -b needed since the branch already exists) - - Rebase the topic branch to minimize the distance between it and your recently synced main branch: `git rebase main` -(Again, for more detail see https://git-scm.com/book/en/Git-Branching-Rebasing[the Pro Git section on rebasing]). -* **Note** While it is generally recommended to __not__ re-write history by using `push --force`, and we do not do this on `main` (and release) branches in the main repo, we require topic branches for pull requests to be rebased before merging, in order to maintain a clean timeline and avoid "merge" commits. -* If, while rebasing for the merge, we find significant conflicts, we may ask you to rebase and `push --force` to your topic branch after resolving the conflicts. -* Assuming your pull request is merged into the 'upstream' main, you will end up pulling that change into your own main eventually and, at that time, you may decide to delete the topic branch from your local repository and your fork (origin) if you pushed it there. - - to delete the local branch: `git branch -d GH-123` - - to delete the branch from your origin: `git push origin :GH-123` - -== Maintain a linear commit history - -When merging to main, the project __always__ uses fast-forward merges. -As discussed above, when issuing pull requests, please ensure that your commit history is linear. -From the command line you can check this using: - ----- -git log --graph --pretty=oneline ----- - -As this may cause lots of typing, we recommend creating a global alias, e.g. `git logg` for this: - ----- -git config --global alias.logg 'log --graph --pretty=oneline' ----- - -This command, will provide the following output, which in this case shows a nice linear history: - ----- -* c129a02e6c752b49bacd4a445092a44f66c2a1e9 GH-2721 Increase Timers on JDBC Delayer Tests -* 14e556ce23d49229c420632cef608630b1d82e7d GH-2620 Fix Debug Log -* 6140aa7b2cfb6ae309c55a157e94b44e5d0bea4f GH-3037 Fix JDBC MS Discard After Completion -* 077f2b24ea871a3937c513e08241d1c6cb9c9179 Update Spring Social Twitter to 1.0.5 -* 6d4f2b46d859c903881a561c35aa28df68f8faf3 GH-3053 Allow task-executor on -* 56f9581b85a8a40bbcf2461ffc0753212669a68d Update Spring Social Twitter version to 1.0.4 ----- - -If you see intersecting lines, that usually means that you forgot to rebase you branch. -As mentioned earlier, **please rebase against main** before issuing a pull request. - -== Follow the Code Style - -Please, follow with the https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-Framework-Code-Style[Spring Integration Code Style]. - -== Use `@since` tags - -Use `@since` tags for newly-added public API types and methods e.g. - -[source java] ----- -/** - * ... - * - * @author First Last - * - * @since 3.0 - * - * @see ... - */ ----- - -== Use `@author` tags - -Use `@author` tag with your real name, when you change any class e.g. - -[source java] ----- -/** - * ... - * - * @author First Last - */ ----- - - -== Submit JUnit test cases for all behavior changes - -Search the codebase to find related unit tests and add additional `@Test` methods within. -It is also acceptable to submit test cases on a per GH issue basis. - -== Squash commits - -Use `git rebase --interactive`, `git add --patch` and other tools to "squash" multiple commits into atomic changes. -In addition to the man pages for git, there are many resources online to help you understand how these tools work. -However, we do recommend to do this only for the first commit in the PR. -All the subsequent commits added after review should preserve the history for better context of the previous and current changes. - -== Use your real name in git commits - -Please configure git to use your real first and last name for any commits you intend to submit as pull requests. -For example, this is not acceptable: - - Author: Nickname - -Rather, please include your first and last name, properly capitalized, as submitted against the SpringIO contributor license agreement: - - Author: First Last - -This helps ensure traceability against the CLA, and also goes a long way to ensuring useful output from tools like `git shortlog` and others. - -You can configure this globally via the account admin area GitHub (useful for fork-and-edit cases); globally with - - git config --global user.name "First Last" - git config --global user.email user@mail.com - -or locally for the *spring-integration* repository only by omitting the '--global' flag: - - cd spring-integration - git config user.name "First Last" - git config user.email user@mail.com - -== Run all tests prior to submission - -See the https://github.com/spring-projects/spring-integration#checking-out-and-building[checking out and building] section of the README for instructions. -Make sure that all tests pass prior to submitting your pull request. - -== Provide a Link to the GitHub issue in the Associated Pull Request - -Add a GitHub issue link to your first commit comment of the pull request on the last line, so your commit message may look like this: - ----- - GH-1639: Add support - - Fixes: gh-1639 - - * add `` XSD element - * add `SpelFunctionParser` - * add `SpelFunctionRegistrar` to avoid introducing some confused 'Method'-bean - * add `SpelFunctionRegistrar` collaboration with `IntegrationEvaluationContextFactoryBean` - * some refactoring for `IntegrationEvaluationContextFactoryBean` - * polishing some failed tests after this change ----- - -Please, follow Chris Beams' recommendations in regard to the good commit message: https://chris.beams.io/posts/git-commit[How to Write a Git Commit Message]. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 62589edd12a..00000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.adoc b/README.adoc new file mode 100644 index 00000000000..8b01da760e8 --- /dev/null +++ b/README.adoc @@ -0,0 +1,25 @@ += Spring Integration Docs Build + +You're currently viewing the Antora playbook branch. +The playbook branch hosts the docs build that is used to build and publish the production docs site. + +The Spring Integration reference docs are built using https://antora.org[Antora]. +This README covers how to build the docs in a software branch as well as how to build the production docs site locally. + +== Building the Site + +You can build the entire site by invoking the following and then viewing the site at `build/site/index.html` + +[source,bash] +---- +./gradlew antora +---- + +== Building a Specific Branch + +You can build a specific branch and then viewing the branch specific site at `build/site/index.html`. + +[source,bash] +---- +./gradlew antora +---- diff --git a/README.md b/README.md deleted file mode 100644 index 3cc013c90a3..00000000000 --- a/README.md +++ /dev/null @@ -1,133 +0,0 @@ - - -# Spring Integration - -[![Build Status](https://github.com/spring-projects/spring-integration/actions/workflows/ci-snapshot.yml/badge.svg)](https://github.com/spring-projects/spring-integration/actions/workflows/ci-snapshot.yml) -[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring-integration) - -Extends the Spring programming model to support the well-known Enterprise Integration Patterns. -Spring Integration enables lightweight messaging within Spring-based applications and supports integration with external systems via declarative adapters. -Those adapters provide a higher-level of abstraction over Spring’s support for remoting, messaging, and scheduling. -Spring Integration’s primary goal is to provide a simple model for building enterprise integration solutions while maintaining the separation of concerns that is essential for producing maintainable, testable code. - -Using the Spring Framework encourages developers to code using interfaces and use dependency injection (DI) to provide a Plain Old Java Object (POJO) with the dependencies it needs to perform its tasks. -Spring Integration takes this concept one step further, where POJOs are wired together using a messaging paradigm and individual components may not be aware of other components in the application. -Such an application is built by assembling fine-grained reusable components to form a higher level of functionality. -With careful design, these flows can be modularized and also reused at an even higher level. - -In addition to wiring together fine-grained components, Spring Integration provides a wide selection of channel adapters and gateways to communicate with external systems. -Channel Adapters are used for one-way integration (send or receive); gateways are used for request/reply scenarios (inbound or outbound). - -# Installation and Getting Started - -First, you need dependencies in your POM/Gradle: - -```xml - - org.springframework.integration - spring-integration-core - -``` - -which is also pulled transitively if you deal with target protocol channel adapters. -For example for Apache Kafka support you need just this: - -```xml - - org.springframework.integration - spring-integration-kafka - -``` - -For annotations or Java DSL configuration you need to *enable* Spring Integration in the application context: - -```java -@EnableIntegration -@Configuration -public class ExampleConfiguration { - -} -``` - -# Code of Conduct - -Please see our [Code of conduct](https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md). - -# Reporting Security Vulnerabilities - -Please see our [Security policy](https://github.com/spring-projects/spring-integration/security/policy). - -# Documentation - -The Spring Integration maintains reference documentation ([published](https://docs.spring.io/spring-integration/reference/) and [source](src/reference/antora)), GitHub [wiki pages](https://github.com/spring-projects/spring-integration/wiki), and an [API reference](https://docs.spring.io/spring-integration/docs/current/api/). -There are also [guides and tutorials](https://spring.io/guides) across Spring projects. - - -# Checking out and Building - -To check out the project and build from the source, do the following: - - git clone git://github.com/spring-projects/spring-integration.git - cd spring-integration - ./gradlew clean test - - or - - ./gradlew clean testAll - -The latter runs additional tests (those annotated with `@LongRunningIntegrationTest`); it is a more thorough test but takes quite a lot longer to run. - -The test results are captured in `build/reports/tests/test` (or `.../testAll`) under each module (in HTML format). - -Add `--continue` to the command to perform a complete build, even if there are failing tests in some modules; otherwise the build will stop after the current module(s) being built are completed. - -**NOTE:** While Spring Integration runs with Java SE 17 or higher, a Java 17 compiler is required to build the project. - -To build and install jars into your local Maven cache: - - ./gradlew build publishToMavenLocal - -To build api Javadoc (results will be in `build/api`): - - ./gradlew api - -To build the reference documentation (results will be in `build/site`): - - ./gradlew antora - -To build complete distribution including `-dist`, `-docs`, and `-schema` zip files (results will be in `build/distributions`): - - ./gradlew dist - -# Using Eclipse or Spring Tool Suite (with BuildShip Plugin) - -If you have the BuildShip plugin installed, - -*File -> Import -> Gradle -> Existing Gradle Project* - -# Using Eclipse or Spring Tool Suite (when the BuildShip Plugin is not installed) - -To generate Eclipse metadata (.classpath and .project files, etc.), do the following: - - ./gradlew eclipse - -Once complete, you may then import the projects into Eclipse as usual: - - *File -> Import -> General -> Existing projects into workspace* - -Browse to the *'spring-integration'* root directory. All projects should import -free of errors. - -# Using IntelliJ IDEA - -To import the project into IntelliJ IDEA: - -File -> Open... -> and select build.gradle from spring-integration project root directory - -# Guidelines - -See also [Contributor Guidelines](https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc). - -# Resources - -For more information, please visit the Spring Integration website at: [https://spring.io/projects/spring-integration](https://spring.io/projects/spring-integration/) diff --git a/antora-playbook.yml b/antora-playbook.yml new file mode 100644 index 00000000000..d31d64219ae --- /dev/null +++ b/antora-playbook.yml @@ -0,0 +1,41 @@ +antora: + extensions: + - require: '@springio/antora-extensions' + root_component_name: 'integration' + +site: + title: Spring Integration + url: https://docs.spring.io/spring-integration/reference + robots: allow +git: + ensure_git_suffix: false +content: + sources: + - url: https://github.com/spring-projects/spring-integration + # Refname matching: + # https://docs.antora.org/antora/latest/playbook/content-refname-matching/ + branches: [ main,'({6..9}).+({0..9}).x', '!(6.{0..1}.x)' ] + tags: [ 'v({6..9}).+({0..9}).+({0..9})?(-{RC,M}+({0..9}))', '!(v6.{0..1}.+({0..9})?(-{RC,M}+({0..9})))', '!(v6.2.0-M1)', '!(v6.2.0-M2)' ] + start_path: src/reference/antora +asciidoc: + attributes: + page-stackoverflow-url: https://stackoverflow.com/tags/spring-integration + page-related-doc-categories: security + page-related-doc-projects: batch,framework,integration,amqp,kafka + page-pagination: '' + hide-uri-scheme: '@' + tabs-sync-option: '@' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' +urls: + latest_version_segment_strategy: redirect:to + latest_version_segment: '' + redirect_facility: httpd +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip + snapshot: true +runtime: + log: + failure_level: warn diff --git a/build.gradle b/build.gradle index 0b0a05d25d7..ad09d1acab1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,1235 +1,17 @@ -buildscript { - ext.kotlinVersion = '2.2.10' - ext.isCI = System.getenv('GITHUB_ACTION') - repositories { - gradlePluginPortal() - mavenCentral() - if (version.endsWith('SNAPSHOT')) { - maven { url 'https://repo.spring.io/snapshot' } - } - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion" - } -} - plugins { - id 'base' - id 'io.spring.nohttp' version '0.0.11' apply false - id 'io.spring.dependency-management' version '1.1.7' - id 'org.jetbrains.dokka' version '1.9.20' - id 'org.antora' version '1.0.0' - id 'io.spring.antora.generate-antora-yml' version '0.0.1' - id 'com.google.protobuf' version '0.9.5' apply false - id 'io.freefair.aggregate-javadoc' version '8.14.2' - id 'io.spring.nullability' version '0.0.4' apply false -} - -if (isCI) { - apply plugin: 'io.spring.nohttp' - - nohttp { - source.include '**/src/**' - source.exclude '**/bin/**', '**/build/**', '**/out/**', '**/target/**', '**/*.gif', '**/*.jpg', '**/*.png', '**/*.svg', '**/*.ks' - } -} - -description = 'Spring Integration' - -ext { - linkHomepage = 'https://projects.spring.io/spring-integration' - linkCi = 'https://build.spring.io/browse/INT' - linkIssue = 'https://github.com/spring-projects/spring-integration/issues' - linkScmUrl = 'https://github.com/spring-projects/spring-integration' - linkScmConnection = 'scm:git:git://github.com/spring-projects/spring-integration.git' - linkScmDevConnection = 'scm:git:ssh://git@github.com:spring-projects/spring-integration.git' - - apacheSshdVersion = '2.15.0' - artemisVersion = '2.42.0' - aspectjVersion = '1.9.24' - assertjVersion = '3.27.4' - assertkVersion = '0.28.1' - avroVersion = '1.12.0' - awaitilityVersion = '4.3.0' - camelVersion = '4.13.0' - commonsDbcp2Version = '2.13.0' - commonsIoVersion = '2.20.0' - commonsNetVersion = '3.12.0' - curatorVersion = '5.9.0' - debeziumVersion = '3.2.1.Final' - derbyVersion = '10.16.1.1' - ftpServerVersion = '1.2.1' - graalvmVersion = '24.2.2' - greenmailVersion = '2.1.5' - groovyVersion = '4.0.28' - hamcrestVersion = '3.0' - hazelcastVersion = '5.5.0' - hibernateVersion = '7.1.0.Final' - hsqldbVersion = '2.7.4' - h2Version = '2.3.232' - jacksonVersion = '2.20.0' - jackson3Version = '3.0.0-rc8' - jaxbVersion = '4.0.5' - jcifsVersion = '2.1.40' - jeroMqVersion = '0.6.0' - jmsApiVersion = '3.1.0' - jpaApiVersion = '3.2.0' - jrubyVersion = '10.0.2.0' - jsonpathVersion = '2.9.0' - junit4Version = '4.13.2' - junitJupiterVersion = '5.13.4' - kotlinCoroutinesVersion = '1.10.2' - kryoVersion = '5.6.2' - lettuceVersion = '6.8.0.RELEASE' - log4jVersion = '2.25.1' - mailVersion = '2.0.4' - micrometerTracingVersion = '1.6.0-M2' - micrometerVersion = '1.16.0-M2' - mockitoVersion = '5.19.0' - mongoDriverVersion = '5.5.1' - mysqlVersion = '9.4.0' - oracleVersion = '23.9.0.25.07' - pahoMqttClientVersion = '1.2.5' - postgresVersion = '42.7.7' - protobufVersion = '4.32.0' - r2dbch2Version = '1.0.0.RELEASE' - reactorVersion = '2025.0.0-M6' - resilience4jVersion = '2.3.0' - romeToolsVersion = '2.1.0' - rsocketVersion = '1.1.5' - servletApiVersion = '6.1.0' - smackVersion = '4.4.8' - springAmqpVersion = '4.0.0-SNAPSHOT' - springDataVersion = '2025.1.0-M5' - springGraphqlVersion = '2.0.0-SNAPSHOT' - springKafkaVersion = '4.0.0-SNAPSHOT' - springRetryVersion = '2.0.12' - springSecurityVersion = '7.0.0-SNAPSHOT' - springVersion = '7.0.0-SNAPSHOT' - springWsVersion = '5.0.0-SNAPSHOT' - testcontainersVersion = '1.21.3' - tomcatVersion = '11.0.10' - xmlUnitVersion = '2.10.3' - xstreamVersion = '1.4.21' - ztZipVersion = '1.17' - - javaProjects = subprojects - project(':spring-integration-bom') -} - -allprojects { - group = 'org.springframework.integration' - - repositories { - if (project.hasProperty('mavenLocal')) { - mavenLocal() - } - mavenCentral() - maven { url 'https://repo.spring.io/milestone' } - if (version.endsWith('SNAPSHOT')) { - maven { url 'https://repo.spring.io/snapshot' } - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } - } -// maven { url 'https://repo.spring.io/libs-staging-local' } - } - - ext.javadocLinks = [ - 'https://docs.oracle.com/en/java/javase/17/docs/api/', - 'https://jakarta.ee/specifications/platform/10/apidocs/', - 'https://docs.spring.io/spring-framework/docs/current/javadoc-api', - 'https://docs.spring.io/spring-amqp/docs/current/api/', - 'https://docs.spring.io/spring-data/data-mongo/docs/current/api/', - 'https://docs.spring.io/spring-data/data-redis/docs/current/api/', - 'https://docs.spring.io/spring-ws/docs/current/api/' - ] as String[] - - apply plugin: 'io.spring.dependency-management' - - dependencyManagement { - resolutionStrategy { - cacheChangingModulesFor 0, 'seconds' - } - applyMavenExclusions = false - generatedPomCustomization { - enabled = false - } - - imports { - mavenBom "com.fasterxml.jackson:jackson-bom:$jacksonVersion" - mavenBom "tools.jackson:jackson-bom:$jackson3Version" - mavenBom "io.micrometer:micrometer-bom:$micrometerVersion" - mavenBom "io.micrometer:micrometer-tracing-bom:$micrometerTracingVersion" - mavenBom "io.projectreactor:reactor-bom:$reactorVersion" - mavenBom "org.apache.camel:camel-bom:$camelVersion" - mavenBom "org.apache.groovy:groovy-bom:$groovyVersion" - mavenBom "org.apache.logging.log4j:log4j-bom:$log4jVersion" - mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:$kotlinCoroutinesVersion" - mavenBom "org.junit:junit-bom:$junitJupiterVersion" - mavenBom "org.mockito:mockito-bom:$mockitoVersion" - mavenBom "org.springframework.amqp:spring-amqp-bom:$springAmqpVersion" - mavenBom "org.springframework.data:spring-data-bom:$springDataVersion" - mavenBom "org.springframework.kafka:spring-kafka-bom:$springKafkaVersion" - mavenBom "org.springframework.security:spring-security-bom:$springSecurityVersion" - mavenBom "org.springframework:spring-framework-bom:$springVersion" - mavenBom "org.springframework.ws:spring-ws-bom:$springWsVersion" - mavenBom "org.testcontainers:testcontainers-bom:$testcontainersVersion" - mavenBom "org.mongodb:mongodb-driver-bom:$mongoDriverVersion" - } - - } - - // TODO until https://github.com/Kotlin/dokka/issues/3472 - configurations.matching { it.name.startsWith('dokka') }.configureEach { - resolutionStrategy.eachDependency { - if (requested.group.startsWith('com.fasterxml.jackson')) { - useVersion('2.15.3') - } - } - } - -} - -configure(javaProjects) { subproject -> - apply plugin: 'java-library' - apply plugin: 'eclipse' - apply plugin: 'idea' - apply plugin: 'checkstyle' - apply plugin: 'kotlin' - apply plugin: 'kotlin-spring' - apply plugin: 'io.spring.nullability' - - apply from: "${rootDir}/gradle/publish-maven.gradle" - - def scopeAttribute = Attribute.of('dependency.scope', String) - - configurations { - provided { - attributes { - attribute(scopeAttribute, 'provided') - } - } - - [compileClasspath, testCompileClasspath, testRuntimeClasspath].each { - it.extendsFrom(provided) - } - } - - components.java.with { - it.addVariantsFromConfiguration(configurations.provided) { - mapToMavenScope('compile') // This is temporary. Gradle doesn't natively support the provided scope - } - } - - sourceSets { - test { - resources { - srcDirs = ['src/test/resources', 'src/test/java'] - } - } - } - - java { - toolchain { - languageVersion = JavaLanguageVersion.of(24) - } - withJavadocJar() - withSourcesJar() - registerFeature('optional') { - usingSourceSet(sourceSets.main) - } - } - - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask).configureEach { - compilerOptions { - apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2 - languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2 - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 - javaParameters = true - allWarningsAsErrors = true - freeCompilerArgs = [ '-Xannotation-default-target=param-property' ] // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255 - } - } - - tasks.withType(JavaCompile).configureEach { - options.fork = true - sourceCompatibility = JavaVersion.VERSION_17 - options.encoding = 'UTF-8' - } - - tasks.withType(Javadoc) { - options.addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint - options.addBooleanOption('Werror', true) // fail build on Javadoc warnings - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/java.util=ALL-UNNAMED' - } - - eclipse { - project { - natures += 'org.springframework.ide.eclipse.core.springnature' - } - } - - // dependencies that are common across all java projects - dependencies { - attributesSchema { - attribute(scopeAttribute) - } - - if (!(subproject.name ==~ /.*-test.*/)) { - testImplementation(project(':spring-integration-test-support')) { - exclude group: 'org.hamcrest' - } - } - - if (subproject.name !in ['spring-integration-test-support', 'spring-integration-core']) { - api project(':spring-integration-core') - } - - testImplementation("org.awaitility:awaitility:$awaitilityVersion") { - exclude group: 'org.hamcrest' - } - testImplementation 'org.junit.jupiter:junit-jupiter-api' - testImplementation 'org.junit.jupiter:junit-jupiter-params' - testImplementation("com.willowtreeapps.assertk:assertk-jvm:$assertkVersion") { - exclude group: 'org.jetbrains.kotlin' - } - testImplementation 'org.jetbrains.kotlin:kotlin-reflect' - testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - testImplementation 'io.projectreactor:reactor-test' - testImplementation 'org.testcontainers:junit-jupiter' - - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - testRuntimeOnly 'org.apache.logging.log4j:log4j-core' - testRuntimeOnly 'org.apache.logging.log4j:log4j-jcl' - testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl' - testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl' - } - - // enable all compiler warnings; individual projects may customize further - ext.xLintArg = '-Xlint:all,-options,-processing' - [compileJava, compileTestJava]*.options*.compilerArgs = [xLintArg, '-parameters'] - - tasks.register('testAll', Test) { dependsOn['check'] } - - gradle.taskGraph.whenReady { graph -> - if (graph.hasTask(testAll)) { - test.enabled = false - } - } - - tasks.withType(Test).configureEach { - // suppress all console output during testing unless running `gradle -i` - logging.captureStandardOutput(LogLevel.INFO) - - if (name ==~ /(testAll)/) { - systemProperty 'RUN_LONG_INTEGRATION_TESTS', 'true' - } - - environment 'SI_FATAL_WHEN_NO_BEANFACTORY', 'true' - - useJUnitPlatform() - - reports.junitXml.required = false - - enableAssertions = false - - jvmArgs '-Xshare:off' - } - - checkstyle { - configDirectory.set(rootProject.file('src/checkstyle')) - toolVersion = project.hasProperty('checkstyleVersion') ? project.checkstyleVersion : '11.0.0' - } - - jar { - manifest { - attributes( - 'Implementation-Version': project.version, - 'Created-By': "JDK ${System.properties['java.version']} (${System.properties['java.specification.vendor']})", - 'Implementation-Title': subproject.name, - 'Implementation-Vendor-Id': subproject.group, - 'Implementation-Vendor': 'Broadcom Inc.', - 'Implementation-URL': linkHomepage, - 'Automatic-Module-Name': subproject.name.replace('-', '.') // for Jigsaw - ) - } - - from("${rootProject.projectDir}/src/dist") { - include 'notice.txt' - into 'META-INF' - expand(copyright: new Date().format('yyyy'), version: project.version) - } - from("${rootProject.projectDir}") { - include 'LICENSE.txt' - into 'META-INF' - } - } - - tasks.register('checkClasspathForConflicts') { - onlyIf { false } - inputs.files(configurations.runtimeClasspath) - - def ignored = ['module-info.class'] - def errors = ['Found classpath conflicts:\n'] - Map> classpathContents = [:] - - doLast { - inputs.files.each { file -> - new java.util.jar.JarFile(file) - .stream() - .filter { !it.name.startsWith('META-INF/') && it.name.endsWith('.class') && !ignored.contains(it.name) } - .each { classpathContents.computeIfAbsent(it.name, { [] as Set }).add(file.absolutePath) } - } - - def conflicts = classpathContents.findAll { it.value.size() > 1 } - - conflicts.each { - errors += " $it.key\n" - it.value.each { - errors += " $it\n" - } - } - - if (errors.size() > 1) { - throw new InvalidUserDataException(errors.toString().replaceAll(~/,\s/, '') - '[' - ']') - } - } - } - - check.dependsOn checkClasspathForConflicts, javadoc - - publishing { - publications { - mavenJava(MavenPublication) { - suppressAllPomMetadataWarnings() - from components.java - pom.withXml { - def pomDeps = asNode().dependencies.first() - subproject.configurations.provided.allDependencies.each { dep -> - pomDeps.'*'.find { it.artifactId.text() == dep.name }.scope.first().value = 'provided' - } - } - } - } - } -} - -project('spring-integration-test-support') { - description = 'Spring Integration Test Support - **No SI Dependencies Allowed**' - dependencies { - api "org.hamcrest:hamcrest-library:$hamcrestVersion" - api 'org.mockito:mockito-core' - api "org.assertj:assertj-core:$assertjVersion" - api 'org.springframework:spring-context' - api 'org.springframework:spring-messaging' - api 'org.springframework:spring-test' - optionalApi("junit:junit:$junit4Version") { - exclude group: 'org.hamcrest' - } - optionalApi 'org.junit.jupiter:junit-jupiter-api' - optionalApi 'org.apache.logging.log4j:log4j-core' - } -} - -project('spring-integration-amqp') { - description = 'Spring Integration AMQP Support' - dependencies { - api 'org.springframework.amqp:spring-rabbit' - optionalApi 'org.springframework.amqp:spring-rabbit-stream' - - testImplementation 'org.springframework.amqp:spring-rabbit-junit' - testImplementation project(':spring-integration-stream') - testImplementation 'org.springframework:spring-web' - testImplementation 'org.testcontainers:rabbitmq' - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - } -} - -project('spring-integration-camel') { - description = 'Spring Integration support for Apache Camel' - - dependencies { - api 'org.apache.camel:camel-core-model' - - testImplementation 'org.apache.camel:camel-test-junit5' - testImplementation('org.apache.camel:camel-spring') { - exclude group: 'org.springframework' - } - } -} - -project('spring-integration-cassandra') { - description = 'Spring Integration Support for Apache Cassandra' - - dependencies { - api 'org.springframework.data:spring-data-cassandra' - - testImplementation 'org.testcontainers:cassandra' - testRuntimeOnly "commons-io:commons-io:$commonsIoVersion" - } -} - -project('spring-integration-core') { - description = 'Spring Integration Core' - - apply plugin: 'org.jetbrains.dokka' - apply plugin: 'com.google.protobuf' - - dependencies { - api 'org.springframework:spring-aop' - api 'org.springframework:spring-context' - api 'org.springframework:spring-messaging' - api 'org.springframework:spring-tx' - api("org.springframework.retry:spring-retry:$springRetryVersion") { - exclude group: 'org.springframework' - } - api 'io.projectreactor:reactor-core' - api 'io.micrometer:micrometer-observation' - - optionalApi 'tools.jackson.core:jackson-databind' - optionalApi('tools.jackson.module:jackson-module-kotlin') { - exclude group: 'org.jetbrains.kotlin' - } - optionalApi 'com.fasterxml.jackson.core:jackson-databind' - optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' - optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' - optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-joda' - optionalApi('com.fasterxml.jackson.module:jackson-module-kotlin') { - exclude group: 'org.jetbrains.kotlin' - } - optionalApi "com.google.protobuf:protobuf-java:$protobufVersion" - optionalApi "com.jayway.jsonpath:json-path:$jsonpathVersion" - optionalApi "com.esotericsoftware:kryo:$kryoVersion" - optionalApi 'io.micrometer:micrometer-core' - optionalApi('io.micrometer:micrometer-tracing') { - exclude group: 'aopalliance' - } - optionalApi "io.github.resilience4j:resilience4j-ratelimiter:$resilience4jVersion" - optionalApi "org.apache.avro:avro:$avroVersion" - optionalApi 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor' - - testImplementation "com.google.protobuf:protobuf-java-util:$protobufVersion" - testImplementation "org.aspectj:aspectjweaver:$aspectjVersion" - testImplementation 'io.micrometer:micrometer-observation-test' - testImplementation('io.micrometer:micrometer-tracing-integration-test') { - exclude group: 'io.opentelemetry' - exclude group: 'com.wavefront' - exclude group: 'io.micrometer', module: 'micrometer-tracing-bridge-otel' - } - } - - dokkaHtmlPartial { - outputDirectory.set(new File('build', 'kdoc')) - dokkaSourceSets { - main { - sourceRoots.setFrom(file('src/main/kotlin')) - classpath.from(sourceSets['main'].runtimeClasspath) - externalDocumentationLink { - url.set(new URL("https://docs.spring.io/spring-integration/docs/$version/api/")) - packageListUrl.set(file('build/docs/javadoc/element-list').toURI().toURL()) - } - externalDocumentationLink { - url.set(new URL('https://projectreactor.io/docs/core/release/api/')) - } - externalDocumentationLink { - url.set(new URL('https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/')) - } - } - } - } - - protobuf { - protoc { - artifact = "com.google.protobuf:protoc:$protobufVersion" - } - } - -} - -project('spring-integration-debezium') { - description = 'Spring Integration Debezium Support' - dependencies { - api("io.debezium:debezium-embedded:$debeziumVersion") { - exclude group: 'org.glassfish.jersey.containers', module: 'jersey-container-servlet' - exclude group: 'org.glassfish.jersey.inject', module: 'jersey-hk2' - exclude group: 'jakarta.xml.bind', module: 'jakarta.xml.bind-api' - exclude group: 'jakarta.activation', module: 'jakarta.activation-api' - exclude group: 'javax.activation', module: 'javax.activation-api' - } - - testImplementation "io.debezium:debezium-connector-mysql:$debeziumVersion" - testImplementation 'org.testcontainers:mysql' - } -} - -project('spring-integration-event') { - description = 'Spring Integration ApplicationEvent Support' -} - -project('spring-integration-feed') { - description = 'Spring Integration RSS Feed Support' - dependencies { - api "com.rometools:rome:$romeToolsVersion" - } -} - -project('spring-integration-file') { - description = 'Spring Integration File Support' - dependencies { - api "commons-io:commons-io:$commonsIoVersion" - optionalApi 'com.fasterxml.jackson.core:jackson-annotations' - - testImplementation project(':spring-integration-redis') - testImplementation project(':spring-integration-redis').sourceSets.test.output - testImplementation project(':spring-integration-jdbc') - testImplementation "com.h2database:h2:$h2Version" - testImplementation "io.lettuce:lettuce-core:$lettuceVersion" - testImplementation "com.jayway.jsonpath:json-path:$jsonpathVersion" - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - } -} - -project('spring-integration-ftp') { - description = 'Spring Integration FTP Support' - dependencies { - api project(':spring-integration-file') - api "commons-net:commons-net:$commonsNetVersion" - api 'org.springframework:spring-context-support' - optionalApi "org.apache.ftpserver:ftpserver-core:$ftpServerVersion" - - testImplementation project(':spring-integration-file').sourceSets.test.output - } -} - -project('spring-integration-graphql') { - description = 'Spring Integration GraphQL Support' - dependencies { - api "org.springframework.graphql:spring-graphql:$springGraphqlVersion" - - testImplementation 'org.springframework:spring-web' - } -} - -project('spring-integration-groovy') { - description = 'Spring Integration Groovy Support' - - apply plugin: 'groovy' - - dependencies { - api project(':spring-integration-scripting') - api 'org.apache.groovy:groovy' - api 'org.springframework:spring-context-support' - - testImplementation 'org.springframework:spring-web' - - testRuntimeOnly 'org.apache.groovy:groovy-dateutil' - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/java.lang=ALL-UNNAMED' - } -} - -project('spring-integration-hazelcast') { - description = 'Spring Integration Hazelcast Support' - dependencies { - api "com.hazelcast:hazelcast:$hazelcastVersion" - - testImplementation project(':spring-integration-jmx') - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-modules', 'java.se', - '--add-exports', 'java.base/jdk.internal.ref=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', - '--add-opens', 'java.management/sun.management=ALL-UNNAMED', - '--add-opens', 'jdk.management/com.sun.management.internal=ALL-UNNAMED', - '-Dhazelcast.logging.type=log4j2' - } -} - -project('spring-integration-http') { - description = 'Spring Integration HTTP Support' - dependencies { - api 'org.springframework:spring-webmvc' - provided "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - optionalApi "com.rometools:rome:$romeToolsVersion" - optionalApi 'org.springframework:spring-webflux' - - testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion" - testImplementation 'org.springframework.security:spring-security-messaging' - testImplementation 'org.springframework.security:spring-security-config' - testImplementation 'org.springframework.security:spring-security-test' - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - - testRuntimeOnly "com.jayway.jsonpath:json-path:$jsonpathVersion" - } -} - -project('spring-integration-ip') { - description = 'Spring Integration IP Support' - dependencies { - testImplementation project(':spring-integration-stream') - testImplementation project(':spring-integration-event') - - testRuntimeOnly "com.esotericsoftware:kryo:$kryoVersion" - testRuntimeOnly 'com.fasterxml.jackson.core:jackson-databind' - testRuntimeOnly 'tools.jackson.core:jackson-databind' - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/java.nio.channels.spi=ALL-UNNAMED' - } -} - -project('spring-integration-jdbc') { - description = 'Spring Integration JDBC Support' - dependencies { - api 'org.springframework:spring-jdbc' - optionalApi "org.postgresql:postgresql:$postgresVersion" - - testImplementation "com.h2database:h2:$h2Version" - testImplementation "org.hsqldb:hsqldb:$hsqldbVersion" - testImplementation "org.apache.derby:derby:$derbyVersion" - testImplementation "org.apache.derby:derbytools:$derbyVersion" - testImplementation "org.apache.derby:derbyclient:$derbyVersion" - testImplementation "org.postgresql:postgresql:$postgresVersion" - testImplementation "com.mysql:mysql-connector-j:$mysqlVersion" - testImplementation("org.apache.commons:commons-dbcp2:$commonsDbcp2Version") { - exclude group: 'commons-logging' - } - testImplementation 'org.testcontainers:mysql' - testImplementation 'org.testcontainers:postgresql' - testImplementation 'org.testcontainers:oracle-xe' - - testRuntimeOnly 'com.fasterxml.jackson.core:jackson-databind' - testRuntimeOnly 'tools.jackson.core:jackson-databind' - testRuntimeOnly "com.oracle.database.jdbc:ojdbc11:$oracleVersion" - } -} - -project('spring-integration-jms') { - description = 'Spring Integration JMS Support' - dependencies { - api 'org.springframework:spring-jms' - provided "jakarta.jms:jakarta.jms-api:$jmsApiVersion" - - testImplementation("org.apache.activemq:artemis-server:$artemisVersion") { - exclude group: 'org.jboss.logmanager' - } - testImplementation "org.apache.activemq:artemis-jakarta-client:$artemisVersion" - testImplementation 'org.springframework:spring-oxm' - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - testImplementation 'io.micrometer:micrometer-observation-test' - } -} - -project('spring-integration-jmx') { - description = 'Spring Integration JMX Support' - dependencies { - testImplementation "org.aspectj:aspectjweaver:$aspectjVersion" - } -} - -project('spring-integration-jpa') { - description = 'Spring Integration JPA Support' - dependencies { - api 'org.springframework:spring-orm' - optionalApi "jakarta.persistence:jakarta.persistence-api:$jpaApiVersion" - - testImplementation 'org.springframework.data:spring-data-jpa' - testImplementation "com.h2database:h2:$h2Version" - testImplementation "org.hibernate.orm:hibernate-core:$hibernateVersion" - } -} - -project('spring-integration-kafka') { - description = 'Spring Integration for Apache Kafka' - dependencies { - api 'org.springframework.kafka:spring-kafka' - - testImplementation ('org.springframework.kafka:spring-kafka-test') { - exclude group: 'ch.qos.logback' - } - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - } -} - -project('spring-integration-mail') { - description = 'Spring Integration Mail Support' - dependencies { - api 'org.springframework:spring-context-support' - - provided "org.eclipse.angus:jakarta.mail:$mailVersion" - - testImplementation "com.icegreen:greenmail:$greenmailVersion" - - testRuntimeOnly 'org.apache.logging.log4j:log4j-jul' - } -} - -project('spring-integration-mongodb') { - description = 'Spring Integration MongoDB Support' - dependencies { - api 'org.springframework.data:spring-data-mongodb' - - optionalApi 'org.mongodb:mongodb-driver-sync' - optionalApi 'org.mongodb:mongodb-driver-reactivestreams' - - testImplementation 'org.testcontainers:mongodb' - } -} - -project('spring-integration-r2dbc') { - description = 'Spring Integration R2DBC Support' - dependencies { - api 'org.springframework.data:spring-data-r2dbc' - - testImplementation "io.r2dbc:r2dbc-h2:$r2dbch2Version" - } -} - -project('spring-integration-mqtt') { - description = 'Spring Integration MQTT Support' - dependencies { - optionalApi "org.eclipse.paho:org.eclipse.paho.client.mqttv3:$pahoMqttClientVersion" - optionalApi "org.eclipse.paho:org.eclipse.paho.mqttv5.client:$pahoMqttClientVersion" - - testImplementation project(':spring-integration-jmx') - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - } -} - -project('spring-integration-redis') { - description = 'Spring Integration Redis Support' - dependencies { - api 'org.springframework.data:spring-data-redis' - - testImplementation "io.lettuce:lettuce-core:$lettuceVersion" - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - } -} - -project('spring-integration-rsocket') { - description = 'Spring Integration RSocket Support' - dependencies { - api "io.rsocket:rsocket-transport-netty:$rsocketVersion" - } -} - -project('spring-integration-scripting') { - description = 'Spring Integration Scripting Support' - dependencies { - optionalApi 'org.jetbrains.kotlin:kotlin-scripting-jsr223' - provided "org.graalvm.sdk:graal-sdk:$graalvmVersion" - provided "org.graalvm.polyglot:js:$graalvmVersion" - provided "org.graalvm.polyglot:python:$graalvmVersion" - - testImplementation "org.jruby:jruby-complete:$jrubyVersion" - testImplementation 'org.apache.groovy:groovy-jsr223' - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', - '--add-opens', 'java.base/java.io=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '-Dpolyglot.engine.WarnInterpreterOnly=false' - } -} - -project('spring-integration-sftp') { - description = 'Spring Integration SFTP Support' - dependencies { - api project(':spring-integration-file') - api 'org.springframework:spring-context-support' - api("org.apache.sshd:sshd-sftp:$apacheSshdVersion") { - exclude group: 'org.slf4j', module: 'jcl-over-slf4j' - } - - testImplementation project(':spring-integration-event') - testImplementation project(':spring-integration-file').sourceSets.test.output - - testRuntimeOnly 'net.i2p.crypto:eddsa:0.3.0' - } -} - -project('spring-integration-smb') { - description = 'Spring Integration SMB Support' - dependencies { - api project(':spring-integration-file') - api "org.codelibs:jcifs:$jcifsVersion" - - testImplementation project(':spring-integration-file').sourceSets.test.output - } -} - -project('spring-integration-stomp') { - description = 'Spring Integration STOMP Support' - dependencies { - optionalApi 'org.springframework:spring-websocket' - - testImplementation project(':spring-integration-websocket') - testImplementation project(':spring-integration-websocket').sourceSets.test.output - testImplementation project(':spring-integration-event') - testImplementation("org.apache.activemq:artemis-stomp-protocol:$artemisVersion") { - exclude group: 'org.jboss.logmanager' - exclude group: 'io.netty' - } - testImplementation "org.apache.tomcat.embed:tomcat-embed-websocket:$tomcatVersion" - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - - testRuntimeOnly 'org.springframework:spring-webmvc' - testRuntimeOnly 'io.projectreactor.netty:reactor-netty-http' - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/java.io=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '--add-opens', 'java.rmi/sun.rmi.transport=ALL-UNNAMED' - } -} - -project('spring-integration-stream') { - description = 'Spring Integration Stream Support' - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/java.io=ALL-UNNAMED' - } -} - -project('spring-integration-syslog') { - description = 'Spring Integration Syslog Support' - dependencies { - api project(':spring-integration-ip') - } -} - -project('spring-integration-test') { - description = 'Spring Integration Testing Framework' - dependencies { - api project(':spring-integration-test-support') - } -} - -project('spring-integration-webflux') { - description = 'Spring Integration HTTP Support' - dependencies { - api(project(':spring-integration-http')) { - exclude group: 'org.springframework', module: 'spring-webmvc' - } - api 'org.springframework:spring-webflux' - optionalApi 'io.projectreactor.netty:reactor-netty-http' - - testImplementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion" - testImplementation 'org.springframework:spring-webmvc' - testImplementation 'org.springframework.security:spring-security-config' - testImplementation 'org.springframework.security:spring-security-test' - testImplementation 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'tools.jackson.core:jackson-databind' - testImplementation 'io.micrometer:micrometer-observation-test' - testImplementation('io.micrometer:micrometer-tracing-integration-test') { - exclude group: 'io.opentelemetry' - exclude group: 'com.wavefront' - exclude group: 'io.micrometer', module: 'micrometer-tracing-bridge-otel' - } - - testRuntimeOnly "com.jayway.jsonpath:json-path:$jsonpathVersion" - } -} - -project('spring-integration-websocket') { - description = 'Spring Integration WebSockets Support' - dependencies { - api 'org.springframework:spring-websocket' - optionalApi 'org.springframework:spring-webmvc' - provided "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - - testImplementation project(':spring-integration-event') - testImplementation "org.apache.tomcat.embed:tomcat-embed-websocket:$tomcatVersion" - - testRuntimeOnly 'com.fasterxml.jackson.core:jackson-databind' - testRuntimeOnly 'tools.jackson.core:jackson-databind' - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.base/java.io=ALL-UNNAMED', - '--add-opens', 'java.base/java.lang=ALL-UNNAMED', - '--add-opens', 'java.rmi/sun.rmi.transport=ALL-UNNAMED' - } -} - -project('spring-integration-ws') { - description = 'Spring Integration Web Services Support' - dependencies { - api 'org.springframework:spring-oxm' - api 'org.springframework:spring-webmvc' - api('org.springframework.ws:spring-ws-core') { - exclude group: 'org.springframework' - exclude group: 'org.glassfish.jaxb' - } - - provided "com.sun.xml.bind:jaxb-impl:$jaxbVersion" - - testImplementation "com.thoughtworks.xstream:xstream:$xstreamVersion" - testImplementation('org.springframework.ws:spring-ws-support') { - exclude group: 'org.springframework' - } - testImplementation 'org.springframework:spring-jms' - testImplementation "jakarta.jms:jakarta.jms-api:$jmsApiVersion" - testImplementation "org.igniterealtime.smack:smack-tcp:$smackVersion" - testImplementation "org.igniterealtime.smack:smack-extensions:$smackVersion" - testImplementation "org.eclipse.angus:angus-mail:$mailVersion" - } -} - -project('spring-integration-xml') { - description = 'Spring Integration XML Support' - dependencies { - api 'org.springframework:spring-oxm' - api('org.springframework.ws:spring-xml') { - exclude group: 'org.springframework' - } - optionalApi('org.springframework.ws:spring-ws-core') { - exclude group: 'org.springframework' - } - - testImplementation "com.sun.xml.bind:jaxb-impl:$jaxbVersion" - testImplementation "org.xmlunit:xmlunit-assertj3:$xmlUnitVersion" - } - - tasks.withType(JavaForkOptions) { - jvmArgs '--add-opens', 'java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED' - } -} - -project('spring-integration-xmpp') { - description = 'Spring Integration XMPP Support' - dependencies { - api "org.igniterealtime.smack:smack-tcp:$smackVersion" - api "org.igniterealtime.smack:smack-java8:$smackVersion" - api "org.igniterealtime.smack:smack-extensions:$smackVersion" - - testImplementation project(':spring-integration-stream') - testImplementation "org.igniterealtime.smack:smack-experimental:$smackVersion" - } -} - -project('spring-integration-zeromq') { - description = 'Spring Integration ZeroMQ Support' - dependencies { - api "org.zeromq:jeromq:$jeroMqVersion" - - optionalApi 'com.fasterxml.jackson.core:jackson-databind' - optionalApi 'tools.jackson.core:jackson-databind' - } -} - -project('spring-integration-zip') { - description = 'Spring Integration Zip Support' - dependencies { - api project(':spring-integration-file') - api "org.zeroturnaround:zt-zip:$ztZipVersion" - } -} - - -project('spring-integration-zookeeper') { - description = 'Spring Integration Zookeeper Support' - dependencies { - api "org.apache.curator:curator-recipes:$curatorVersion" - - testImplementation "org.apache.curator:curator-test:$curatorVersion" - } -} - -project('spring-integration-bom') { - description = 'Spring Integration (Bill of Materials)' - - apply plugin: 'java-platform' - apply from: "${rootDir}/gradle/publish-maven.gradle" - - dependencies { - constraints { - javaProjects.sort { "$it.name" }.each { - api it - } - } - } - - publishing { - publications { - mavenJava(MavenPublication) { - from components.javaPlatform - } - } - } -} - -dependencies { - javaProjects.each { - javadoc it - } -} - -javadoc { - title = "${rootProject.description} ${version} API" - options { - encoding = 'UTF-8' - memberLevel = JavadocMemberLevel.PROTECTED - author = true - header = project.description - use = true - overview = 'src/api/overview.html' - splitIndex = true - links(project.ext.javadocLinks) - addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint - } - - destinationDir = file('build/api') - classpath = files().from { files(javaProjects.collect { it.sourceSets.main.compileClasspath }) } -} - -tasks.register('api') { - group = 'Documentation' - description = 'Generates aggregated Javadoc API documentation.' - dependsOn javadoc -} - -dokkaHtmlMultiModule { - dependsOn 'api' - moduleName.set('spring-integration') - outputDirectory.set(file('build/kdoc')) -} - -apply from: "${rootDir}/gradle/docs.gradle" - -tasks.register('schemaZip', Zip) { - group = 'Distribution' - archiveClassifier = 'schema' - description = "Builds -${archiveClassifier} archive containing all " + - "XSDs for deployment at static.springframework.org/schema." - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - - javaProjects.each { subproject -> - Properties schemas = new Properties() - def shortName = subproject.name.replaceFirst("${rootProject.name}-", '') - if (subproject.name.endsWith('-core')) { - shortName = '' - } - - subproject.sourceSets.main.resources.find { - it.path.endsWith("META-INF${File.separator}spring.schemas") - }?.withInputStream { schemas.load(it) } - - for (def key : schemas.keySet()) { - File xsdFile = subproject.sourceSets.main.resources.find { - it.path.replaceAll('\\\\', '/').endsWith(schemas.get(key)) - } - assert xsdFile != null - into("integration/$shortName") { - from xsdFile.path - rename { String fileName -> - String[] versionNumbers = project.version.split(/\./, 3) - fileName.replace('.xsd', "-${versionNumbers[0]}.${versionNumbers[1]}.xsd") - } - } - } - } -} - -tasks.register('docsZip', Zip) { - dependsOn 'dokkaHtmlMultiModule' - group = 'Distribution' - archiveClassifier = 'docs' - description = "Builds -${archiveClassifier} archive containing api and reference " + - "for deployment at static.springframework.org/spring-integration/docs." - - from('src/dist') { - include 'changelog.txt' - } - - from(javadoc) { - into 'api' - } - - from(dokkaHtmlMultiModule.outputDirectory) { - into 'kdoc-api' - } -} - -tasks.register('distZip', Zip) { - dependsOn 'docsZip' - dependsOn 'schemaZip' - group = 'Distribution' - archiveClassifier = 'dist' - description = "Builds -${archiveClassifier} archive, containing all jars and docs, " + - "suitable for community download page." - - ext.baseDir = "${project.name}-${project.version}" - - from('src/dist') { - include 'readme.txt' - include 'notice.txt' - into "${baseDir}" - expand(copyright: new Date().format('yyyy'), version: project.version) - } - - from("$project.rootDir") { - include 'LICENSE.txt' - into "${baseDir}" - } - - from(zipTree(docsZip.archiveFile)) { - into "${baseDir}/docs" - } - - from(zipTree(schemaZip.archiveFile)) { - into "${baseDir}/schema" - } - - javaProjects.each { subproject -> - into("${baseDir}/libs") { - from subproject.jar - from subproject.sourcesJar - from subproject.javadocJar - } - } - - from(project(':spring-integration-bom').generatePomFileForMavenJavaPublication) { - into "${baseDir}/libs" - rename 'pom-default.xml', "spring-integration-bom-${project.version}.xml" - } -} - -tasks.register('dist') { - dependsOn assemble - group = 'Distribution' - description = 'Builds -dist, -docs and -schema distribution archives.' -} - -apply from: "${rootDir}/gradle/publish-maven.gradle" - -publishing { - publications { - mavenJava(MavenPublication) { - artifact docsZip - artifact schemaZip - artifact distZip - } - } + id 'base' + id 'org.antora' version '1.0.0' +} + +antora { + version = '3.2.0-alpha.2' + options = [clean: true, fetch: true, stacktrace: true] + // NOTE remember to update the versions in lib/antora/templates/per-branch-antora-playbook.yml as well + dependencies = [ + '@antora/atlas-extension' : '1.0.0-alpha.1', + '@antora/collector-extension' : '1.0.0-alpha.3', + '@asciidoctor/tabs' : '1.0.0-beta.3', + '@springio/antora-extensions' : '1.11.1', + '@springio/asciidoctor-extensions': '1.0.0-alpha.10', + ] } diff --git a/gradle.properties b/gradle.properties index f9e7adb3b5a..4845ebc5e43 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,2 @@ -version=7.0.0-SNAPSHOT -org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -kotlin.jvm.target.validation.mode=IGNORE -org.gradle.caching=true -org.gradle.parallel=true -kotlin.stdlib.default.dependency=false +group=org.springframework.integration +description=Spring Integration Docs Site diff --git a/gradle/docs.gradle b/gradle/docs.gradle deleted file mode 100644 index b17f809524c..00000000000 --- a/gradle/docs.gradle +++ /dev/null @@ -1,67 +0,0 @@ -ext { - micrometerDocsVersion='1.0.2' -} - - -antora { - version = '3.2.0-alpha.2' - playbook = file('src/reference/antora/antora-playbook.yml') - options = [ - 'to-dir' : project.layout.buildDirectory.dir('site').get().toString(), - clean: true, - fetch: !project.gradle.startParameter.offline, - stacktrace: true - ] - dependencies = [ - '@antora/atlas-extension': '1.0.0-alpha.2', - '@antora/collector-extension': '1.0.0-beta.3', - '@asciidoctor/tabs': '1.0.0-beta.6', - '@springio/antora-extensions': '1.14.2', - '@springio/asciidoctor-extensions': '1.0.0-alpha.14', - ] -} - -tasks.named('generateAntoraYml') { - asciidocAttributes = project.provider( { - return ['project-version' : project.version ] - } ) - baseAntoraYmlFile = file('src/reference/antora/antora.yml') -} - -tasks.register('createAntoraPartials', Sync) { - from tasks.filterMetricsDocsContent.outputs - into layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/partials') -} - -tasks.register('generateAntoraResources') { - dependsOn 'createAntoraPartials' - dependsOn 'generateAntoraYml' -} - -configurations { - micrometerDocs -} - -dependencies { - micrometerDocs "io.micrometer:micrometer-docs-generator:$micrometerDocsVersion" -} - -def observationInputDir = file('spring-integration-core/src/main/java/org/springframework/integration/support/management/observation').absolutePath -def generatedDocsDir = file('build/reference/generated').absolutePath - -tasks.register('generateObservabilityDocs', JavaExec) { - inputs.dir(observationInputDir) - outputs.dir(generatedDocsDir) - classpath configurations.micrometerDocs - args observationInputDir, /.+/, generatedDocsDir - mainClass = 'io.micrometer.docs.DocsGeneratorCommand' -} - -tasks.register('filterMetricsDocsContent', Copy) { - dependsOn generateObservabilityDocs - from generatedDocsDir - include '_*.adoc' - into generatedDocsDir - rename { filename -> filename.replace '_', '' } - filter { line -> line.replaceAll('org.springframework.integration', 'o.s.i') } -} \ No newline at end of file diff --git a/gradle/publish-maven.gradle b/gradle/publish-maven.gradle deleted file mode 100644 index 1388fce23ce..00000000000 --- a/gradle/publish-maven.gradle +++ /dev/null @@ -1,63 +0,0 @@ -apply plugin: 'maven-publish' - -publishing { - publications { - mavenJava(MavenPublication) { - pom { - afterEvaluate { - name = project.description - description = project.description - } - url = linkScmUrl - organization { - name = 'Spring IO' - url = 'https://spring.io/projects/spring-integration' - } - licenses { - license { - name = 'Apache License, Version 2.0' - url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' - distribution = 'repo' - } - } - scm { - url = linkScmUrl - connection = linkScmConnection - developerConnection = linkScmDevConnection - } - developers { - developer { - id = 'artembilan' - name = 'Artem Bilan' - email = 'artem.bilan@broadcom.com' - roles = ['project lead'] - } - developer { - id = 'garyrussell' - name = 'Gary Russell' - email = 'github@gprussell.net' - roles = ['project lead emeritus'] - } - developer { - id = 'markfisher' - name = 'Mark Fisher' - email = 'mark.ryan.fisher@gmail.com' - roles = ['project founder and lead emeritus'] - } - } - issueManagement { - system = 'GitHub' - url = linkIssue - } - } - versionMapping { - usage('java-api') { - fromResolutionResult() - } - usage('java-runtime') { - fromResolutionResult() - } - } - } - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baab..7f93135c49b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 78cb6e16a49..46671acb6e1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a93670..1aa94a42690 100755 --- a/gradlew +++ b/gradlew @@ -15,8 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# SPDX-License-Identifier: Apache-2.0 -# ############################################################################## # @@ -57,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -86,7 +84,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +112,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -205,7 +203,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -213,7 +211,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat old mode 100755 new mode 100644 index 5eed7ee8452..6689b85beec --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,6 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -45,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail @@ -59,22 +57,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail :execute @rem Setup the command line -set CLASSPATH= +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 772b0627228..00000000000 --- a/settings.gradle +++ /dev/null @@ -1,19 +0,0 @@ -pluginManagement { - repositories { - gradlePluginPortal() - mavenCentral() - } -} - -plugins { - id 'io.spring.develocity.conventions' version '0.0.24' - id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' -} - -rootProject.name = 'spring-integration' - -rootDir.eachDir { dir -> - if (dir.name.startsWith('spring-integration-')) { - include ":${dir.name}" - } -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/AbstractAmqpChannel.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/AbstractAmqpChannel.java deleted file mode 100644 index 4bf9e28f5df..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/AbstractAmqpChannel.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import java.util.Objects; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AmqpAdmin; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.rabbit.connection.Connection; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionListener; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.converter.AllowedListDeserializingMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.MappingUtils; -import org.springframework.integration.channel.AbstractMessageChannel; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.message.AdviceMessage; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.MutableMessageHeaders; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.Assert; - -/** - * The base {@link AbstractMessageChannel} implementation for AMQP. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * - * @since 2.1 - */ -public abstract class AbstractAmqpChannel extends AbstractMessageChannel implements ConnectionListener { - - private final AmqpTemplate amqpTemplate; - - private final @Nullable RabbitTemplate rabbitTemplate; - - private final AmqpHeaderMapper outboundHeaderMapper; - - private final AmqpHeaderMapper inboundHeaderMapper; - - private @Nullable AmqpAdmin admin; - - private @Nullable ConnectionFactory connectionFactory; - - private boolean extractPayload; - - private boolean loggingEnabled = true; - - private @Nullable MessageDeliveryMode defaultDeliveryMode; - - private boolean headersMappedLast; - - private volatile boolean initialized; - - /** - * Construct an instance with the supplied template and default header mappers - * used if the template is a {@link RabbitTemplate} and the message is mapped. - * @param amqpTemplate the template. - * @see #setExtractPayload(boolean) - */ - AbstractAmqpChannel(AmqpTemplate amqpTemplate) { - this(amqpTemplate, DefaultAmqpHeaderMapper.outboundMapper(), DefaultAmqpHeaderMapper.inboundMapper()); - } - - /** - * Construct an instance with the supplied template and header mappers, used - * when the message is mapped. - * @param amqpTemplate the template. - * @param outboundMapper the outbound mapper. - * @param inboundMapper the inbound mapper. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - AbstractAmqpChannel(AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) { - Assert.notNull(amqpTemplate, "amqpTemplate must not be null"); - this.amqpTemplate = amqpTemplate; - if (amqpTemplate instanceof RabbitTemplate castRabbitTemplate) { - this.rabbitTemplate = castRabbitTemplate; - MessageConverter converter = this.rabbitTemplate.getMessageConverter(); - if (converter instanceof AllowedListDeserializingMessageConverter allowedListMessageConverter) { - allowedListMessageConverter.addAllowedListPatterns( - "java.util*", - "java.lang*", - GenericMessage.class.getName(), - ErrorMessage.class.getName(), - AdviceMessage.class.getName(), - MutableMessage.class.getName(), - MessageHeaders.class.getName(), - MutableMessageHeaders.class.getName(), - MessageHistory.class.getName()); - } - } - else { - this.rabbitTemplate = null; - } - this.outboundHeaderMapper = outboundMapper; - this.inboundHeaderMapper = inboundMapper; - } - - @Override - public boolean isLoggingEnabled() { - return this.loggingEnabled; - } - - @Override - public void setLoggingEnabled(boolean loggingEnabled) { - this.loggingEnabled = loggingEnabled; - } - - /** - * Set the delivery mode to use if the message has no - * {@value org.springframework.amqp.support.AmqpHeaders#DELIVERY_MODE} - * header and the message property was not set by the - * {@code MessagePropertiesConverter}. - * @param defaultDeliveryMode the default delivery mode. - * @since 4.3 - */ - public void setDefaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) { - this.defaultDeliveryMode = defaultDeliveryMode; - } - - /** - * Set to true to extract the payload and map the headers; otherwise - * the entire message is converted and sent. Default false. - * @param extractPayload true to extract and map. - * @since 4.3 - */ - public void setExtractPayload(boolean extractPayload) { - if (extractPayload) { - Assert.isTrue(this.rabbitTemplate != null, "amqpTemplate must be a RabbitTemplate for 'extractPayload'"); - } - this.extractPayload = extractPayload; - } - - /** - * @return the extract payload. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - protected boolean isExtractPayload() { - return this.extractPayload; - } - - /** - * When mapping headers for the outbound message, determine whether the headers are - * mapped before the message is converted, or afterward. This only affects headers - * that might be added by the message converter. When false, the converter's headers - * win; when true, any headers added by the converter will be overridden (if the - * source message has a header that maps to those headers). You might wish to set this - * to true, for example, when using a - * {@link org.springframework.amqp.support.converter.SimpleMessageConverter} with a - * String payload that contains json; the converter will set the content type to - * {@code text/plain} which can be overridden to {@code application/json} by setting - * the {@link org.springframework.amqp.support.AmqpHeaders#CONTENT_TYPE} message header. - * Only applies when {@link #setExtractPayload(boolean) extractPayload} is true. - * Default: false. - * @param headersMappedLast true if headers are mapped after conversion. - * @since 5.0 - */ - public void setHeadersMappedLast(boolean headersMappedLast) { - this.headersMappedLast = headersMappedLast; - } - - /** - * Subclasses may override this method to return an Exchange name. - * By default, Messages will be sent to the no-name Direct Exchange. - * @return The exchange name. - */ - protected String getExchangeName() { - return ""; - } - - /** - * Subclasses may override this method to return a routing key. - * By default, there will be no routing key (empty string). - * @return The routing key. - */ - protected String getRoutingKey() { - return ""; - } - - protected AmqpHeaderMapper getInboundHeaderMapper() { - return this.inboundHeaderMapper; - } - - protected AmqpTemplate getAmqpTemplate() { - return this.amqpTemplate; - } - - protected RabbitTemplate getRabbitTemplate() { - Assert.notNull(this.rabbitTemplate, "The 'RabbitTemplate' must be provided."); - return this.rabbitTemplate; - } - - protected final void setAdmin(AmqpAdmin admin) { - this.admin = admin; - } - - protected final void setConnectionFactory(ConnectionFactory connectionFactory) { - this.connectionFactory = connectionFactory; - } - - protected @Nullable AmqpAdmin getAdmin() { - return this.admin; - } - - protected @Nullable ConnectionFactory getConnectionFactory() { - return this.connectionFactory; - } - - @Override - protected void onInit() { - super.onInit(); - if (!this.initialized && this.rabbitTemplate != null && this.connectionFactory != null) { - this.connectionFactory.addConnectionListener(this); - } - this.initialized = true; - } - - @Override - public void destroy() { - if (this.connectionFactory != null) { - this.connectionFactory.removeConnectionListener(this); - this.initialized = false; - } - } - - @Override - protected boolean doSend(Message message, long timeout) { - if (this.extractPayload) { - this.amqpTemplate.send(getExchangeName(), getRoutingKey(), MappingUtils.mapMessage(message, - getRabbitTemplate().getMessageConverter(), Objects.requireNonNull(this.outboundHeaderMapper), - this.defaultDeliveryMode, this.headersMappedLast)); - } - else { - this.amqpTemplate.convertAndSend(getExchangeName(), getRoutingKey(), message); - } - return true; - } - - @Override - public void onCreate(@Nullable Connection connection) { - doDeclares(); - } - - protected abstract void doDeclares(); - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/AbstractSubscribableAmqpChannel.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/AbstractSubscribableAmqpChannel.java deleted file mode 100644 index 5c8f58b0bda..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/AbstractSubscribableAmqpChannel.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.AmqpConnectException; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.MessageListener; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.integration.MessageDispatchingException; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.dispatcher.AbstractDispatcher; -import org.springframework.integration.dispatcher.MessageDispatcher; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.management.ManageableSmartLifecycle; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.util.Assert; - -/** - * The base {@link AbstractAmqpChannel} extension for a {@link SubscribableChannel} contract. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel - implements SubscribableChannel, ManageableSmartLifecycle { - - private final String channelName; - - private final AbstractMessageListenerContainer container; - - @SuppressWarnings("NullAway.Init") - private volatile AbstractDispatcher dispatcher; - - private final boolean isPubSub; - - private volatile @Nullable Integer maxSubscribers; - - private volatile boolean declared; - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @see #setExtractPayload(boolean) - */ - protected AbstractSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate) { - - this(channelName, container, amqpTemplate, false); - } - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @param outboundMapper the outbound mapper. - * @param inboundMapper the inbound mapper. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - protected AbstractSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) { - - this(channelName, container, amqpTemplate, false, outboundMapper, inboundMapper); - } - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @param isPubSub true for a pub/sub channel. - * @see #setExtractPayload(boolean) - */ - protected AbstractSubscribableAmqpChannel(String channelName, - AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate, boolean isPubSub) { - - this(channelName, container, amqpTemplate, isPubSub, - DefaultAmqpHeaderMapper.outboundMapper(), DefaultAmqpHeaderMapper.inboundMapper()); - } - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @param isPubSub true for a pub/sub channel. - * @param outboundMapper the outbound mapper. - * @param inboundMapper the inbound mapper. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - protected AbstractSubscribableAmqpChannel(String channelName, - AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate, boolean isPubSub, - AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) { - - super(amqpTemplate, outboundMapper, inboundMapper); - Assert.notNull(container, "container must not be null"); - Assert.hasText(channelName, "channel name must not be empty"); - this.channelName = channelName; - this.container = container; - this.isPubSub = isPubSub; - ConnectionFactory connectionFactory = container.getConnectionFactory(); - setConnectionFactory(connectionFactory); - setAdmin(new RabbitAdmin(connectionFactory)); - } - - /** - * Specify the maximum number of subscribers supported by the - * channel's dispatcher (if it is an {@link AbstractDispatcher}). - * @param maxSubscribers The maximum number of subscribers allowed. - */ - public void setMaxSubscribers(int maxSubscribers) { - this.maxSubscribers = maxSubscribers; - AbstractDispatcher dispatcherToUse = this.dispatcher; - if (dispatcherToUse != null) { - dispatcherToUse.setMaxSubscribers(maxSubscribers); - } - } - - @Override - public boolean subscribe(MessageHandler handler) { - return this.dispatcher.addHandler(handler); - } - - @Override - public boolean unsubscribe(MessageHandler handler) { - return this.dispatcher.removeHandler(handler); - } - - @Override - public void onInit() { - super.onInit(); - this.dispatcher = createDispatcher(); - Integer maxSubscribersToCheck = this.maxSubscribers; - if (maxSubscribersToCheck == null) { - int newMaxSubscribers = - this.isPubSub - ? getIntegrationProperties().getChannelsMaxBroadcastSubscribers() - : getIntegrationProperties().getChannelsMaxUnicastSubscribers(); - setMaxSubscribers(newMaxSubscribers); - } - else { - this.dispatcher.setMaxSubscribers(maxSubscribersToCheck); - } - String queue = obtainQueueName(this.channelName); - this.container.setQueueNames(queue); - MessageConverter converter = - (getAmqpTemplate() instanceof RabbitTemplate rabbitTemplate) - ? rabbitTemplate.getMessageConverter() - : new SimpleMessageConverter(); - - MessageListener listener = - new DispatchingMessageListener(converter, this.dispatcher, this, this.isPubSub, - getMessageBuilderFactory(), getInboundHeaderMapper()); - this.container.setMessageListener(listener); - if (!this.container.isActive()) { - this.container.afterPropertiesSet(); - } - } - - /* - * SmartLifecycle implementation (delegates to the MessageListener container) - */ - - @Override - public boolean isAutoStartup() { - return this.container.isAutoStartup(); - } - - @Override - public int getPhase() { - return this.container.getPhase(); - } - - @Override - public boolean isRunning() { - return this.container.isRunning(); - } - - @Override - public void start() { - if (!this.declared) { - try { - doDeclares(); - this.declared = true; - } - catch (AmqpConnectException e) { - logger.info("Broker not available; cannot check queue declarations. " + - "Postponed to the next connection create..."); - } - } - this.container.start(); - } - - @Override - public void stop() { - if (isRunning()) { - this.container.stop(); - this.declared = false; - } - } - - @Override - public void stop(Runnable callback) { - if (isRunning()) { - this.container.stop(callback); - this.declared = false; - } - else { - callback.run(); - } - } - - @Override - public void destroy() { - super.destroy(); - this.container.destroy(); - this.declared = false; - } - - protected abstract AbstractDispatcher createDispatcher(); - - protected abstract String obtainQueueName(String channelName); - - private static final class DispatchingMessageListener implements MessageListener { - - private final Log logger = LogFactory.getLog(DispatchingMessageListener.class); - - private final MessageDispatcher dispatcher; - - private final MessageConverter converter; - - private final AbstractSubscribableAmqpChannel channel; - - private final boolean isPubSub; - - private final MessageBuilderFactory messageBuilderFactory; - - private final AmqpHeaderMapper inboundHeaderMapper; - - private DispatchingMessageListener(MessageConverter converter, - MessageDispatcher dispatcher, AbstractSubscribableAmqpChannel channel, boolean isPubSub, - MessageBuilderFactory messageBuilderFactory, AmqpHeaderMapper inboundHeaderMapper) { - - Assert.notNull(converter, "MessageConverter must not be null"); - Assert.notNull(dispatcher, "MessageDispatcher must not be null"); - this.converter = converter; - this.dispatcher = dispatcher; - this.channel = channel; - this.isPubSub = isPubSub; - this.messageBuilderFactory = messageBuilderFactory; - this.inboundHeaderMapper = inboundHeaderMapper; - } - - @Override - public void onMessage(org.springframework.amqp.core.Message message) { - Message messageToSend = null; - try { - Object converted = this.converter.fromMessage(message); - messageToSend = (converted instanceof Message) ? (Message) converted - : buildMessage(message, converted); - this.dispatcher.dispatch(messageToSend); - } - catch (MessageDispatchingException e) { - String exceptionMessage = - e.getMessage() + " for amqp-channel '" + this.channel.getFullChannelName() + "'."; - - throw new ListenerExecutionFailedException(exceptionMessage, e, message); - } - } - - private Message buildMessage(org.springframework.amqp.core.Message message, Object converted) { - AbstractIntegrationMessageBuilder messageBuilder = - this.messageBuilderFactory.withPayload(converted); - if (this.channel.isExtractPayload()) { - Map headers = - this.inboundHeaderMapper.toHeadersFromRequest(message.getMessageProperties()); - messageBuilder.copyHeaders(headers); - } - return messageBuilder.build(); - } - - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PointToPointSubscribableAmqpChannel.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PointToPointSubscribableAmqpChannel.java deleted file mode 100644 index ff2d87b526c..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PointToPointSubscribableAmqpChannel.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import org.springframework.amqp.core.AmqpAdmin; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.dispatcher.AbstractDispatcher; -import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy; -import org.springframework.integration.dispatcher.UnicastingDispatcher; - -/** - * The {@link AbstractSubscribableAmqpChannel} implementation for one-to-one subscription - * over AMQP queue. - *

- * If queue name is not provided, the channel bean name is used internally to declare - * a queue via provided {@link AmqpAdmin} (if any). - * - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.1 - */ -public class PointToPointSubscribableAmqpChannel extends AbstractSubscribableAmqpChannel { - - @SuppressWarnings("NullAway.Init") - private volatile Queue queue; - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @see #setExtractPayload(boolean) - */ - public PointToPointSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate) { - - super(channelName, container, amqpTemplate); - } - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @param outboundMapper the outbound mapper. - * @param inboundMapper the inbound mapper. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - public PointToPointSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) { - - super(channelName, container, amqpTemplate, outboundMapper, inboundMapper); - } - - /** - * Provide a Queue name to be used. If this is not provided, - * the Queue's name will be the same as the channel name. - * @param queueName The queue name. - */ - public void setQueueName(String queueName) { - this.queue = new Queue(queueName); - } - - @Override - protected String obtainQueueName(String channelName) { - if (this.queue == null) { - this.queue = new Queue(channelName); - } - - return this.queue.getName(); - } - - @Override - protected AbstractDispatcher createDispatcher() { - UnicastingDispatcher unicastingDispatcher = new UnicastingDispatcher(); - unicastingDispatcher.setLoadBalancingStrategy(new RoundRobinLoadBalancingStrategy()); - return unicastingDispatcher; - } - - @Override - protected String getRoutingKey() { - return this.queue != null ? this.queue.getName() : super.getRoutingKey(); - } - - @Override - protected void doDeclares() { - AmqpAdmin admin = getAdmin(); - if (admin != null && this.queue != null && admin.getQueueProperties(this.queue.getName()) == null) { - admin.declareQueue(this.queue); - } - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PollableAmqpChannel.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PollableAmqpChannel.java deleted file mode 100644 index 12fd0e81bf8..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PollableAmqpChannel.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AmqpAdmin; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.channel.ExecutorChannelInterceptorAware; -import org.springframework.integration.support.management.metrics.CounterFacade; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.ExecutorChannelInterceptor; -import org.springframework.util.Assert; - -/** - * A {@link PollableChannel} implementation that is backed by an AMQP Queue. - * Messages will be sent to the default (no-name) exchange with that Queue's - * name as the routing key. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * - * @since 2.1 - */ -public class PollableAmqpChannel extends AbstractAmqpChannel - implements PollableChannel, ExecutorChannelInterceptorAware { - - private final String channelName; - - @SuppressWarnings("NullAway.Init") - private Queue queue; - - private @Nullable CounterFacade receiveCounter; - - private volatile int executorInterceptorsSize; - - private volatile boolean declared; - - /** - * Construct an instance with the supplied name, template and default header mappers - * used if the template is a {@link RabbitTemplate} and the message is mapped. - * @param channelName the channel name. - * @param amqpTemplate the template. - * @see #setExtractPayload(boolean) - */ - public PollableAmqpChannel(String channelName, AmqpTemplate amqpTemplate) { - super(amqpTemplate); - Assert.hasText(channelName, "channel name must not be empty"); - this.channelName = channelName; - } - - /** - * Construct an instance with the supplied name, template and header mappers. - * @param channelName the channel name. - * @param amqpTemplate the template. - * @param outboundMapper the outbound mapper. - * @param inboundMapper the inbound mapper. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - public PollableAmqpChannel(String channelName, AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, - AmqpHeaderMapper inboundMapper) { - - super(amqpTemplate, outboundMapper, inboundMapper); - Assert.hasText(channelName, "channel name must not be empty"); - this.channelName = channelName; - } - - /** - * Provide an explicitly configured queue name. If this is not provided, then a Queue will be created - * implicitly with the channelName as its name. The implicit creation will require that either an AmqpAdmin - * instance has been provided or that the configured AmqpTemplate is an instance of RabbitTemplate. - * @param queueName The queue name. - */ - public void setQueueName(String queueName) { - this.queue = new Queue(queueName); - } - - /** - * Provide an instance of AmqpAdmin for implicitly declaring Queues if the queueName is not provided. - * When providing a RabbitTemplate implementation, this is not strictly necessary since a RabbitAdmin - * instance can be created from the template's ConnectionFactory reference. - * @param amqpAdmin The amqp admin. - */ - public void setAmqpAdmin(AmqpAdmin amqpAdmin) { - setAdmin(amqpAdmin); - } - - @Override - protected String getRoutingKey() { - return this.queue != null ? this.queue.getName() : super.getRoutingKey(); - } - - @Override - protected void onInit() { - AmqpTemplate amqpTemplate = getAmqpTemplate(); - if (this.queue == null) { - if (getAdmin() == null && amqpTemplate instanceof RabbitTemplate) { - ConnectionFactory connectionFactory = ((RabbitTemplate) amqpTemplate).getConnectionFactory(); - setAdmin(new RabbitAdmin(connectionFactory)); - setConnectionFactory(connectionFactory); - } - - Assert.notNull(getAdmin(), - "If no queueName is configured explicitly, an AmqpAdmin instance must be provided, " + - "or the AmqpTemplate must be a RabbitTemplate since the Queue needs to be declared."); - - this.queue = new Queue(this.channelName); - } - super.onInit(); - } - - @Override - protected void doDeclares() { - AmqpAdmin admin = getAdmin(); - if (admin != null && admin.getQueueProperties(this.queue.getName()) == null) { - admin.declareQueue(this.queue); - } - } - - @Override - public @Nullable Message receive() { - return doReceive(null); - } - - @Override - public @Nullable Message receive(long timeout) { - return doReceive(timeout); - } - - protected @Nullable Message doReceive(@Nullable Long timeout) { - ChannelInterceptorList interceptorList = getIChannelInterceptorList(); - Deque interceptorStack = null; - AtomicBoolean counted = new AtomicBoolean(); - boolean traceEnabled = isLoggingEnabled() && logger.isTraceEnabled(); - try { - if (traceEnabled) { - logger.trace("preReceive on channel '" + this + "'"); - } - if (!interceptorList.getInterceptors().isEmpty()) { - interceptorStack = new ArrayDeque<>(); - if (!interceptorList.preReceive(this, interceptorStack)) { - return null; - } - } - Object object = performReceive(timeout); - Message message = buildMessageFromResult(object, traceEnabled, counted); - - if (message != null) { - message = interceptorList.postReceive(message, this); - } - interceptorList.afterReceiveCompletion(message, this, null, interceptorStack); - return message; - } - catch (RuntimeException ex) { - if (!counted.get()) { - incrementReceiveErrorCounter(ex); - } - interceptorList.afterReceiveCompletion(null, this, ex, interceptorStack); - throw ex; - } - } - - @Nullable - protected Object performReceive(@Nullable Long timeout) { - if (!this.declared) { - doDeclares(); - this.declared = true; - } - - if (!isExtractPayload()) { - if (timeout == null) { - return getAmqpTemplate().receiveAndConvert(this.queue.getName()); - } - else { - return getAmqpTemplate().receiveAndConvert(this.queue.getName(), timeout); - } - } - else { - RabbitTemplate rabbitTemplate = getRabbitTemplate(); - org.springframework.amqp.core.Message message; - if (timeout == null) { - message = rabbitTemplate.receive(this.queue.getName()); - } - else { - message = rabbitTemplate.receive(this.queue.getName(), timeout); - } - - if (message != null) { - Object payload = rabbitTemplate.getMessageConverter().fromMessage(message); - Map headers = getInboundHeaderMapper() - .toHeadersFromRequest(message.getMessageProperties()); - return getMessageBuilderFactory() - .withPayload(payload) - .copyHeaders(headers) - .build(); - } - else { - return null; - } - } - } - - private @Nullable Message buildMessageFromResult( - @Nullable Object object, boolean traceEnabled, AtomicBoolean counted) { - - Message message = null; - if (object != null) { - if (object instanceof Message) { - message = (Message) object; - } - else { - message = getMessageBuilderFactory() - .withPayload(object) - .build(); - } - } - incrementReceiveCounter(); - counted.set(true); - - if (traceEnabled) { - logger.trace("postReceive on channel '" + this - + "', message" + (message != null ? ": " + message : " is null")); - } - - return message; - } - - private void incrementReceiveCounter() { - MetricsCaptor metricsCaptor = getMetricsCaptor(); - if (metricsCaptor != null) { - if (this.receiveCounter == null) { - this.receiveCounter = buildReceiveCounter(metricsCaptor, null); - } - this.receiveCounter.increment(); - } - } - - private void incrementReceiveErrorCounter(Exception ex) { - MetricsCaptor metricsCaptor = getMetricsCaptor(); - if (metricsCaptor != null) { - buildReceiveCounter(metricsCaptor, ex).increment(); - } - } - - private CounterFacade buildReceiveCounter(MetricsCaptor metricsCaptor, @Nullable Exception ex) { - CounterFacade counterFacade = metricsCaptor - .counterBuilder(RECEIVE_COUNTER_NAME) - .tag("name", getComponentName() == null ? "unknown" : getComponentName()) - .tag("type", "channel") - .tag("result", ex == null ? "success" : "failure") - .tag("exception", ex == null ? "none" : ex.getClass().getSimpleName()) - .description("Messages received") - .build(); - this.meters.add(counterFacade); - return counterFacade; - } - - @Override - public void setInterceptors(List interceptors) { - super.setInterceptors(interceptors); - for (ChannelInterceptor interceptor : interceptors) { - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - } - - @Override - public void addInterceptor(ChannelInterceptor interceptor) { - super.addInterceptor(interceptor); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - - @Override - public void addInterceptor(int index, ChannelInterceptor interceptor) { - super.addInterceptor(index, interceptor); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - - @Override - public boolean removeInterceptor(ChannelInterceptor interceptor) { - boolean removed = super.removeInterceptor(interceptor); - if (removed && interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize--; - } - return removed; - } - - @Override - public ChannelInterceptor removeInterceptor(int index) { - ChannelInterceptor interceptor = super.removeInterceptor(index); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize--; - } - return interceptor; - } - - @Override - public boolean hasExecutorInterceptors() { - return this.executorInterceptorsSize > 0; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PublishSubscribeAmqpChannel.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PublishSubscribeAmqpChannel.java deleted file mode 100644 index 52c2625364a..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/PublishSubscribeAmqpChannel.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import org.springframework.amqp.core.AmqpAdmin; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.AnonymousQueue; -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.FanoutExchange; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.channel.BroadcastCapableChannel; -import org.springframework.integration.dispatcher.AbstractDispatcher; -import org.springframework.integration.dispatcher.BroadcastingDispatcher; - -/** - * The {@link AbstractSubscribableAmqpChannel} extension for pub-sub semantics based on the {@link FanoutExchange}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel implements BroadcastCapableChannel { - - private final Queue queue = new AnonymousQueue(); - - @SuppressWarnings("NullAway.Init") - private volatile FanoutExchange exchange; - - @SuppressWarnings("NullAway.Init") - private volatile Binding binding; - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template. - * @see #setExtractPayload(boolean) - */ - public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate) { - - super(channelName, container, amqpTemplate, true); - } - - /** - * Construct an instance with the supplied name, container and template; default header - * mappers will be used if the message is mapped. - * @param channelName the channel name. - * @param container the container. - * @param amqpTemplate the template - * @param outboundMapper the outbound mapper. - * @param inboundMapper the inbound mapper. - * @since 4.3 - * @see #setExtractPayload(boolean) - */ - public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container, - AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) { - - super(channelName, container, amqpTemplate, true, outboundMapper, inboundMapper); - } - - /** - * Configure the FanoutExchange instance. If this is not provided, then a - * FanoutExchange will be declared implicitly, and its name will be the same - * as the channel name prefixed by "si.fanout.". In either case, an effectively - * anonymous Queue will be declared automatically. - * @param exchange The fanout exchange. - */ - public void setExchange(FanoutExchange exchange) { - this.exchange = exchange; - } - - @Override - protected String getExchangeName() { - return (this.exchange != null) ? this.exchange.getName() : ""; - } - - @Override - protected String obtainQueueName(String channelName) { - if (this.exchange == null) { - String exchangeName = "si.fanout." + channelName; - this.exchange = new FanoutExchange(exchangeName); - } - - this.binding = BindingBuilder.bind(this.queue).to(this.exchange); - - return this.queue.getName(); - } - - @Override - protected AbstractDispatcher createDispatcher() { - BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true); - broadcastingDispatcher.setBeanFactory(getBeanFactory()); - return broadcastingDispatcher; - } - - @Override - protected void doDeclares() { - AmqpAdmin admin = getAdmin(); - if (admin != null) { - if (admin.getQueueProperties(this.queue.getName()) == null) { - admin.declareQueue(this.queue); - } - if (this.exchange != null) { - admin.declareExchange(this.exchange); - } - if (this.binding != null) { - admin.declareBinding(this.binding); - } - } - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/package-info.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/package-info.java deleted file mode 100644 index 568c04f347d..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/channel/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to AMQP-backed channels. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.amqp.channel; diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AbstractAmqpInboundAdapterParser.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AbstractAmqpInboundAdapterParser.java deleted file mode 100644 index 7a8fab1f79d..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AbstractAmqpInboundAdapterParser.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.w3c.dom.Element; - -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.beans.factory.xml.XmlReaderContext; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Base class for inbound adapter parsers for the AMQP namespace. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefinitionParser { - - private static final String[] CONTAINER_VALUE_ATTRIBUTES = { - "acknowledge-mode", - "channel-transacted", - "concurrent-consumers", - "consumers-per-queue", - "expose-listener-channel", - "phase", - "prefetch-count", - "queue-names", - "recovery-interval", - "receive-timeout", - "shutdown-timeout", - "batch-size", - "missing-queues-fatal" - }; - - private static final String[] CONTAINER_REFERENCE_ATTRIBUTES = { - "advice-chain", - "connection-factory", - "error-handler", - "message-properties-converter", - "task-executor", - "transaction-attribute", - "transaction-manager" - }; - - private final String adapterClassName; - - AbstractAmqpInboundAdapterParser(String adapterClassName) { - Assert.hasText(adapterClassName, "adapterClassName is required"); - this.adapterClassName = adapterClassName; - } - - @Override - protected final String getBeanClassName(Element element) { - return this.adapterClassName; - } - - @Override - protected final boolean shouldGenerateId() { - return false; - } - - @Override - protected final boolean shouldGenerateIdAsFallback() { - return true; - } - - @Override - protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String listenerContainerRef = element.getAttribute("listener-container"); - if (StringUtils.hasText(listenerContainerRef)) { - assertNoContainerAttributes(element, parserContext); - builder.addConstructorArgReference(listenerContainerRef); - } - else { - BeanDefinition listenerContainerBeanDef = buildListenerContainer(element, parserContext); - builder.addConstructorArgValue(listenerContainerBeanDef); - } - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter"); - - BeanDefinitionBuilder mapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultAmqpHeaderMapper.class); - mapperBuilder.setFactoryMethod("inboundMapper"); - IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, mapperBuilder, null); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "batch-mode"); - configureChannels(element, parserContext, builder); - AbstractBeanDefinition adapterBeanDefinition = builder.getRawBeanDefinition(); - adapterBeanDefinition.setResource(parserContext.getReaderContext().getResource()); - adapterBeanDefinition.setSource(IntegrationNamespaceUtils.createElementDescription(element)); - } - - private BeanDefinition buildListenerContainer(Element element, ParserContext parserContext) { - XmlReaderContext readerContext = parserContext.getReaderContext(); - if (!element.hasAttribute("queue-names")) { - readerContext.error( - "If no 'listener-container' reference is provided, the 'queue-names' attribute is required.", - element); - } - String consumersPerQueue = element.getAttribute("consumers-per-queue"); - BeanDefinitionBuilder builder; - if (StringUtils.hasText(consumersPerQueue)) { - builder = BeanDefinitionBuilder.genericBeanDefinition(DirectMessageListenerContainer.class); - if (StringUtils.hasText(element.getAttribute("concurrent-consumers"))) { - readerContext.error("'consumers-per-queue' and 'concurrent-consumers' are mutually exclusive", element); - } - if (StringUtils.hasText(element.getAttribute("tx-size"))) { - readerContext.error("'tx-size' is not allowed with 'consumers-per-queue'", element); - } - if (StringUtils.hasText(element.getAttribute("receive-timeout"))) { - readerContext.error("'receive-timeout' is not allowed with 'consumers-per-queue'", element); - } - builder.addPropertyValue("consumersPerQueue", consumersPerQueue); - } - else { - builder = BeanDefinitionBuilder.genericBeanDefinition(SimpleMessageListenerContainer.class); - } - String connectionFactoryRef = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactoryRef)) { - connectionFactoryRef = "rabbitConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactoryRef); - for (String attributeName : CONTAINER_VALUE_ATTRIBUTES) { - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, attributeName); - } - for (String attributeName : CONTAINER_REFERENCE_ATTRIBUTES) { - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attributeName); - } - return builder.getBeanDefinition(); - } - - private void assertNoContainerAttributes(Element element, ParserContext parserContext) { - Object source = parserContext.extractSource(element); - List allContainerAttributes = new ArrayList<>(Arrays.asList(CONTAINER_VALUE_ATTRIBUTES)); - allContainerAttributes.addAll(Arrays.asList(CONTAINER_REFERENCE_ATTRIBUTES)); - for (String attributeName : allContainerAttributes) { - if (StringUtils.hasText(element.getAttribute(attributeName))) { - parserContext.getReaderContext().error("Attribute '" + attributeName - + "' is not allowed when a 'listener-container' reference has been provided", source); - } - } - } - - protected abstract void configureChannels(Element element, ParserContext parserContext, - BeanDefinitionBuilder builder); - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpChannelFactoryBean.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpChannelFactoryBean.java deleted file mode 100644 index 571e927e0ea..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpChannelFactoryBean.java +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Executor; - -import org.aopalliance.aop.Advice; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.AmqpAdmin; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.FanoutExchange; -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.RabbitAccessor; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.support.MessagePropertiesConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.config.AbstractFactoryBean; -import org.springframework.context.Lifecycle; -import org.springframework.context.SmartLifecycle; -import org.springframework.integration.JavaUtils; -import org.springframework.integration.amqp.channel.AbstractAmqpChannel; -import org.springframework.integration.amqp.channel.PointToPointSubscribableAmqpChannel; -import org.springframework.integration.amqp.channel.PollableAmqpChannel; -import org.springframework.integration.amqp.channel.PublishSubscribeAmqpChannel; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.interceptor.TransactionAttribute; -import org.springframework.util.Assert; -import org.springframework.util.ErrorHandler; - -/** - * If point-to-point, we send to the default exchange with the routing key - * equal to "[beanName]" and we declare that same Queue and register a listener - * if message-driven or poll explicitly otherwise. If publish-subscribe, we declare - * a FanoutExchange named "si.fanout.[beanName]" and we send to that without any - * routing key, and on the receiving side, we create an anonymous Queue that is - * bound to that exchange. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 2.1 - */ -public class AmqpChannelFactoryBean extends AbstractFactoryBean - implements SmartLifecycle, BeanNameAware { - - private final AmqpTemplate amqpTemplate = new RabbitTemplate(); - - private final boolean messageDriven; - - @SuppressWarnings("NullAway.Init") - private AbstractAmqpChannel channel; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - @SuppressWarnings("NullAway.Init") - private ConnectionFactory connectionFactory; - - private @Nullable List interceptors; - - private @Nullable AmqpAdmin amqpAdmin; - - private @Nullable FanoutExchange exchange; - - private @Nullable String queueName; - - private boolean autoStartup = true; - - private Advice @Nullable [] adviceChain; - - private @Nullable Integer concurrentConsumers; - - private @Nullable Integer consumersPerQueue; - - private @Nullable MessagePropertiesConverter messagePropertiesConverter; - - private @Nullable ErrorHandler errorHandler; - - private @Nullable Boolean exposeListenerChannel; - - private @Nullable Integer phase; - - private @Nullable Integer prefetchCount; - - private boolean isPubSub; - - private @Nullable Long receiveTimeout; - - private @Nullable Long recoveryInterval; - - private @Nullable Long shutdownTimeout; - - private @Nullable AcknowledgeMode acknowledgeMode; - - private boolean channelTransacted; - - private @Nullable Executor taskExecutor; - - private @Nullable PlatformTransactionManager transactionManager; - - private @Nullable TransactionAttribute transactionAttribute; - - private @Nullable Integer batchSize; - - private @Nullable Integer maxSubscribers; - - private @Nullable Boolean missingQueuesFatal; - - private @Nullable MessageDeliveryMode defaultDeliveryMode; - - private @Nullable Boolean extractPayload; - - private AmqpHeaderMapper outboundHeaderMapper = DefaultAmqpHeaderMapper.outboundMapper(); - - private AmqpHeaderMapper inboundHeaderMapper = DefaultAmqpHeaderMapper.inboundMapper(); - - private boolean headersLast; - - public AmqpChannelFactoryBean() { - this(true); - } - - public AmqpChannelFactoryBean(boolean messageDriven) { - this.messageDriven = messageDriven; - } - - @Override - public void setBeanName(String name) { - this.beanName = name; - } - - public void setInterceptors(List interceptors) { - this.interceptors = interceptors; - } - - /** - * This is an optional reference to an AmqpAdmin to use when - * declaring a Queue implicitly for a PollableAmqpChannel. It - * is not needed for the message-driven (Subscribable) channels - * since those are able to create a RabbitAdmin instance using - * the underlying listener container's ConnectionFactory. - * - * @param amqpAdmin The amqp admin. - */ - public void setAmqpAdmin(AmqpAdmin amqpAdmin) { - this.amqpAdmin = amqpAdmin; - } - - /** - * Set the FanoutExchange to use. This is only relevant for - * publish-subscribe-channels, and even then if not provided, - * a FanoutExchange will be implicitly created. - * - * @param exchange The fanout exchange. - */ - public void setExchange(FanoutExchange exchange) { - this.exchange = exchange; - } - - /** - * Set the Queue name to use. This is only relevant for - * point-to-point channels, even then if not provided, - * a Queue will be implicitly created. - * - * @param queueName The queue name. - */ - public void setQueueName(String queueName) { - this.queueName = queueName; - } - - /* - * Template-only properties - */ - - public void setEncoding(String encoding) { - if (this.amqpTemplate instanceof RabbitTemplate rabbitTemplate) { - rabbitTemplate.setEncoding(encoding); - } - else if (logger.isInfoEnabled()) { - logger.info("AmqpTemplate is not a RabbitTemplate, so configured 'encoding' value will be ignored."); - } - } - - public void setMessageConverter(MessageConverter messageConverter) { - if (this.amqpTemplate instanceof RabbitTemplate rabbitTemplate) { - rabbitTemplate.setMessageConverter(messageConverter); - } - else if (logger.isInfoEnabled()) { - logger.info("AmqpTemplate is not a RabbitTemplate, so configured MessageConverter will be ignored."); - } - } - - public void setTemplateChannelTransacted(boolean channelTransacted) { - if (this.amqpTemplate instanceof RabbitTemplate rabbitTemplate) { - rabbitTemplate.setChannelTransacted(channelTransacted); - } - else if (logger.isInfoEnabled()) { - logger.info("AmqpTemplate is not a RabbitTemplate, so configured 'channelTransacted' will be ignored."); - } - } - - /* - * Template and Container properties - */ - - public void setChannelTransacted(boolean channelTransacted) { - this.channelTransacted = channelTransacted; - } - - public void setConnectionFactory(ConnectionFactory connectionFactory) { - this.connectionFactory = connectionFactory; - if (this.amqpTemplate instanceof RabbitTemplate rabbitTemplate) { - rabbitTemplate.setConnectionFactory(this.connectionFactory); - } - } - - public void setMessagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) { - this.messagePropertiesConverter = messagePropertiesConverter; - if (this.amqpTemplate instanceof RabbitTemplate rabbitTemplate) { - rabbitTemplate.setMessagePropertiesConverter(messagePropertiesConverter); - } - } - - /* - * Container-only properties - */ - - public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; - } - - public void setAdviceChain(Advice[] adviceChain) { - this.adviceChain = Arrays.copyOf(adviceChain, adviceChain.length); - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public void setConcurrentConsumers(int concurrentConsumers) { - this.concurrentConsumers = concurrentConsumers; - } - - public void setConsumersPerQueue(Integer consumersPerQueue) { - this.consumersPerQueue = consumersPerQueue; - } - - public void setErrorHandler(ErrorHandler errorHandler) { - this.errorHandler = errorHandler; - } - - public void setExposeListenerChannel(boolean exposeListenerChannel) { - this.exposeListenerChannel = exposeListenerChannel; - } - - public void setPhase(int phase) { - this.phase = phase; - } - - public void setPrefetchCount(int prefetchCount) { - this.prefetchCount = prefetchCount; - } - - public void setPubSub(boolean pubSub) { - this.isPubSub = pubSub; - } - - public void setReceiveTimeout(long receiveTimeout) { - this.receiveTimeout = receiveTimeout; - } - - public void setRecoveryInterval(long recoveryInterval) { - this.recoveryInterval = recoveryInterval; - } - - public void setShutdownTimeout(long shutdownTimeout) { - this.shutdownTimeout = shutdownTimeout; - } - - public void setTaskExecutor(Executor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - public void setTransactionAttribute(TransactionAttribute transactionAttribute) { - this.transactionAttribute = transactionAttribute; - } - - public void setTransactionManager(PlatformTransactionManager transactionManager) { - this.transactionManager = transactionManager; - } - - public void setBatchSize(Integer batchSize) { - this.batchSize = batchSize; - } - - public void setMaxSubscribers(int maxSubscribers) { - this.maxSubscribers = maxSubscribers; - } - - public void setMissingQueuesFatal(Boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - - public void setDefaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) { - this.defaultDeliveryMode = defaultDeliveryMode; - } - - public void setExtractPayload(Boolean extractPayload) { - this.extractPayload = extractPayload; - } - - public void setOutboundHeaderMapper(AmqpHeaderMapper outboundMapper) { - this.outboundHeaderMapper = outboundMapper; - } - - public void setInboundHeaderMapper(AmqpHeaderMapper inboundMapper) { - this.inboundHeaderMapper = inboundMapper; - } - - public void setHeadersLast(boolean headersLast) { - this.headersLast = headersLast; - } - - @Override - public Class getObjectType() { - return (this.channel != null) ? this.channel.getClass() : AbstractAmqpChannel.class; - } - - @Override - protected AbstractAmqpChannel createInstance() { - if (this.messageDriven) { - AbstractMessageListenerContainer container = this.createContainer(); - if (this.amqpTemplate instanceof RabbitAccessor rabbitAccessor) { - rabbitAccessor.afterPropertiesSet(); - } - if (this.isPubSub) { - PublishSubscribeAmqpChannel pubsub = new PublishSubscribeAmqpChannel( - this.beanName, container, this.amqpTemplate, this.outboundHeaderMapper, - this.inboundHeaderMapper); - JavaUtils.INSTANCE - .acceptIfNotNull(this.exchange, pubsub::setExchange) - .acceptIfNotNull(this.maxSubscribers, pubsub::setMaxSubscribers); - this.channel = pubsub; - } - else { - PointToPointSubscribableAmqpChannel p2p = new PointToPointSubscribableAmqpChannel( - this.beanName, container, this.amqpTemplate, this.outboundHeaderMapper, - this.inboundHeaderMapper); - JavaUtils.INSTANCE - .acceptIfHasText(this.queueName, p2p::setQueueName) - .acceptIfNotNull(this.maxSubscribers, p2p::setMaxSubscribers); - this.channel = p2p; - } - } - else { - Assert.isTrue(!this.isPubSub, "An AMQP 'publish-subscribe-channel' must be message-driven."); - PollableAmqpChannel pollable = new PollableAmqpChannel(this.beanName, this.amqpTemplate, - this.outboundHeaderMapper, this.inboundHeaderMapper); - JavaUtils.INSTANCE - .acceptIfNotNull(this.amqpAdmin, pollable::setAmqpAdmin) - .acceptIfHasText(this.queueName, pollable::setQueueName); - this.channel = pollable; - } - JavaUtils.INSTANCE - .acceptIfNotEmpty(this.interceptors, this.channel::setInterceptors); - this.channel.setBeanName(this.beanName); - JavaUtils.INSTANCE - .acceptIfNotNull(getBeanFactory(), this.channel::setBeanFactory) - .acceptIfNotNull(this.defaultDeliveryMode, this.channel::setDefaultDeliveryMode) - .acceptIfNotNull(this.extractPayload, this.channel::setExtractPayload); - this.channel.setHeadersMappedLast(this.headersLast); - this.channel.afterPropertiesSet(); - return this.channel; - } - - private AbstractMessageListenerContainer createContainer() { - AbstractMessageListenerContainer container; - if (this.consumersPerQueue == null) { - SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer(); - JavaUtils.INSTANCE - .acceptIfNotNull(this.concurrentConsumers, smlc::setConcurrentConsumers) - .acceptIfNotNull(this.receiveTimeout, smlc::setReceiveTimeout) - .acceptIfNotNull(this.batchSize, smlc::setBatchSize); - container = smlc; - } - else { - DirectMessageListenerContainer dmlc = new DirectMessageListenerContainer(); - dmlc.setConsumersPerQueue(this.consumersPerQueue); - container = dmlc; - } - JavaUtils.INSTANCE - .acceptIfNotNull(this.acknowledgeMode, container::setAcknowledgeMode) - .acceptIfNotEmpty(this.adviceChain, container::setAdviceChain); - container.setAutoStartup(this.autoStartup); - container.setChannelTransacted(this.channelTransacted); - container.setConnectionFactory(this.connectionFactory); - - JavaUtils.INSTANCE - .acceptIfNotNull(this.errorHandler, container::setErrorHandler) - .acceptIfNotNull(this.exposeListenerChannel, container::setExposeListenerChannel) - .acceptIfNotNull(this.messagePropertiesConverter, container::setMessagePropertiesConverter) - .acceptIfNotNull(this.phase, container::setPhase) - .acceptIfNotNull(this.prefetchCount, container::setPrefetchCount) - .acceptIfNotNull(this.recoveryInterval, container::setRecoveryInterval) - .acceptIfNotNull(this.shutdownTimeout, container::setShutdownTimeout) - .acceptIfNotNull(this.taskExecutor, container::setTaskExecutor) - .acceptIfNotNull(this.transactionAttribute, container::setTransactionAttribute) - .acceptIfNotNull(this.transactionManager, container::setTransactionManager) - .acceptIfNotNull(this.missingQueuesFatal, container::setMissingQueuesFatal); - return container; - } - - /* - * SmartLifecycle implementation (delegates to the created channel if message-driven) - */ - - @Override - public boolean isAutoStartup() { - return (this.channel instanceof SmartLifecycle smartLifecycle) && smartLifecycle.isAutoStartup(); - } - - @Override - public int getPhase() { - return (this.channel instanceof SmartLifecycle smartLifecycle) ? - smartLifecycle.getPhase() : 0; - } - - @Override - public boolean isRunning() { - return (this.channel instanceof Lifecycle lifecycle) && lifecycle.isRunning(); - } - - @Override - public void start() { - if (this.channel instanceof Lifecycle lifecycle) { - lifecycle.start(); - } - } - - @Override - public void stop() { - if (this.channel instanceof Lifecycle lifecycle) { - lifecycle.stop(); - } - } - - @Override - public void stop(Runnable callback) { - if (this.channel instanceof SmartLifecycle smartLifecycle) { - smartLifecycle.stop(callback); - } - else { - callback.run(); - } - } - - @Override - protected void destroyInstance(@Nullable AbstractAmqpChannel instance) { - this.channel.destroy(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpChannelParser.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpChannelParser.java deleted file mode 100644 index 1a735331a16..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpChannelParser.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractChannelParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.StringUtils; - -/** - * Parser for the 'channel' and 'publish-subscribe-channel' elements of the - * Spring Integration AMQP namespace. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class AmqpChannelParser extends AbstractChannelParser { - - @Override - protected BeanDefinitionBuilder buildBeanDefinition(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AmqpChannelFactoryBean.class); - String messageDriven = element.getAttribute("message-driven"); - if (StringUtils.hasText(messageDriven)) { - builder.addConstructorArgValue(messageDriven); - } - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "rabbitConnectionFactory"; - } - builder.addPropertyReference("connectionFactory", connectionFactory); - - builder.addPropertyValue("pubSub", "publish-subscribe-channel".equals(element.getLocalName())); - - populateConsumersPerQueueIfAny(element, parserContext, builder); - - String[] valuesToPopulate = { - "max-subscribers", - "acknowledge-mode", - "auto-startup", - "channel-transacted", - "template-channel-transacted", - "concurrent-consumers", - "encoding", - "expose-listener-channel", - "phase", - "prefetch-count", - "queue-name", - "receive-timeout", - "recovery-interval", - "missing-queues-fatal", - "shutdown-timeout", - "tx-size", - "default-delivery-mode", - "extract-payload", - "headers-last" - }; - - for (String attribute : valuesToPopulate) { - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, attribute); - } - - String[] referencesToPopulate = { - "advice-chain", - "amqp-admin", - "error-handler", - "exchange", - "message-converter", - "message-properties-converter", - "task-executor", - "transaction-attribute", - "transaction-manager", - "outbound-header-mapper", - "inbound-header-mapper" - }; - - for (String attribute : referencesToPopulate) { - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attribute); - } - - return builder; - } - - private void populateConsumersPerQueueIfAny(Element element, ParserContext parserContext, - BeanDefinitionBuilder builder) { - - String consumersPerQueue = element.getAttribute("consumers-per-queue"); - if (StringUtils.hasText(consumersPerQueue)) { - if (StringUtils.hasText(element.getAttribute("concurrent-consumers"))) { - parserContext.getReaderContext() - .error("'consumers-per-queue' and 'concurrent-consumers' are mutually exclusive", element); - } - if (StringUtils.hasText(element.getAttribute("tx-size"))) { - parserContext.getReaderContext().error("'tx-size' is not allowed with 'consumers-per-queue'", element); - } - if (StringUtils.hasText(element.getAttribute("receive-timeout"))) { - parserContext.getReaderContext() - .error("'receive-timeout' is not allowed with 'consumers-per-queue'", element); - } - builder.addPropertyValue("consumersPerQueue", consumersPerQueue); - } - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParser.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParser.java deleted file mode 100644 index 715903a8814..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParser.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.util.StringUtils; - -/** - * Parser for the AMQP 'inbound-channel-adapter' element. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class AmqpInboundChannelAdapterParser extends AbstractAmqpInboundAdapterParser { - - AmqpInboundChannelAdapterParser() { - super(AmqpInboundChannelAdapter.class.getName()); - } - - @Override - protected final String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) - throws BeanDefinitionStoreException { - - String id = element.getAttribute("id"); - if (!element.hasAttribute("channel")) { - // the created channel will get the 'id', so the adapter's bean name includes a suffix - id = id + ".adapter"; - } - else if (!StringUtils.hasText(id)) { - id = parserContext.getReaderContext().generateBeanName(definition); - } - return id; - } - - @Override - protected void configureChannels(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String channelName = element.getAttribute("channel"); - if (!StringUtils.hasText(channelName)) { - channelName = this.createDirectChannel(element, parserContext); - } - builder.addPropertyReference("outputChannel", channelName); - } - - private String createDirectChannel(Element element, ParserContext parserContext) { - String channelId = element.getAttribute("id"); - if (!StringUtils.hasText(channelId)) { - parserContext.getReaderContext().error("The channel-adapter's 'id' attribute is required when no 'channel' " - + "reference has been provided, because that 'id' would be used for the created channel.", element); - } - BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.genericBeanDefinition(DirectChannel.class); - BeanDefinitionHolder holder = new BeanDefinitionHolder(channelBuilder.getBeanDefinition(), channelId); - BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry()); - return channelId; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParser.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParser.java deleted file mode 100644 index d0c0cec17b7..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParser.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.amqp.inbound.AmqpInboundGateway; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.StringUtils; - -/** - * Parser for the AMQP 'inbound-gateway' element. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class AmqpInboundGatewayParser extends AbstractAmqpInboundAdapterParser { - - AmqpInboundGatewayParser() { - super(AmqpInboundGateway.class.getName()); - } - - @Override - protected void configureChannels(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String amqpTemplateRef = element.getAttribute("amqp-template"); - if (StringUtils.hasText(amqpTemplateRef)) { - builder.addConstructorArgReference(amqpTemplateRef); - } - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-channel"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-reply-to"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-headers-last", - "replyHeadersMappedLast"); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpNamespaceHandler.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpNamespaceHandler.java deleted file mode 100644 index 60df2d56e01..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpNamespaceHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * Namespace handler for the AMQP schema. - * - * @author Mark Fisher - * @author Gary Russell - * - * @since 2.1 - */ -public class AmqpNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - @Override - public void init() { - registerBeanDefinitionParser("channel", new AmqpChannelParser()); - registerBeanDefinitionParser("publish-subscribe-channel", new AmqpChannelParser()); - registerBeanDefinitionParser("inbound-channel-adapter", new AmqpInboundChannelAdapterParser()); - registerBeanDefinitionParser("inbound-gateway", new AmqpInboundGatewayParser()); - registerBeanDefinitionParser("outbound-channel-adapter", new AmqpOutboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-gateway", new AmqpOutboundGatewayParser()); - registerBeanDefinitionParser("outbound-async-gateway", new AmqpOutboundGatewayParser()); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParser.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParser.java deleted file mode 100644 index ea72903936c..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParser.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.StringUtils; - -/** - * Parser for the AMQP 'outbound-channel-adapter' element. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class AmqpOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AmqpOutboundEndpoint.class); - String amqpTemplateRef = element.getAttribute("amqp-template"); - if (!StringUtils.hasText(amqpTemplateRef)) { - amqpTemplateRef = "amqpTemplate"; - if (StringUtils.hasText(element.getAttribute("return-channel")) - || StringUtils.hasText(element.getAttribute("confirm-correlation-expression"))) { - parserContext.getReaderContext().error("A dedicated 'amqp-template' is required when" + - " using publisher confirms and returns", element); - } - } - builder.addConstructorArgReference(amqpTemplateRef); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "exchange-name", true); - BeanDefinition exchangeNameExpression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("exchange-name-expression", element); - if (exchangeNameExpression != null) { - builder.addPropertyValue("exchangeNameExpression", exchangeNameExpression); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "routing-key", true); - BeanDefinition routingKeyExpression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("routing-key-expression", element); - if (routingKeyExpression != null) { - builder.addPropertyValue("routingKeyExpression", routingKeyExpression); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-delivery-mode"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "lazy-connect"); - - BeanDefinitionBuilder mapperBuilder = BeanDefinitionBuilder - .genericBeanDefinition(DefaultAmqpHeaderMapper.class); - mapperBuilder.setFactoryMethod("outboundMapper"); - IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, - mapperBuilder, null); - - BeanDefinition confirmCorrelationExpression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("confirm-correlation-expression", element); - if (confirmCorrelationExpression != null) { - builder.addPropertyValue("confirmCorrelationExpression", confirmCorrelationExpression); - } - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "confirm-ack-channel"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "confirm-nack-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "confirm-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "wait-for-confirm"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "multi-send"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "return-channel"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-message-strategy"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "delay-expression", - "delayExpressionString"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "headers-last", "headersMappedLast"); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParser.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParser.java deleted file mode 100644 index 94bd0851915..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParser.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; -import org.springframework.integration.amqp.outbound.AsyncAmqpOutboundGateway; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.StringUtils; - -/** - * Parser for the AMQP 'outbound-channel-adapter' element. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gunnar Hillert - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.1 - */ -public class AmqpOutboundGatewayParser extends AbstractConsumerEndpointParser { - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder; - boolean async = element.getLocalName().contains("async"); - if (async) { - builder = BeanDefinitionBuilder.genericBeanDefinition(AsyncAmqpOutboundGateway.class); - String asyncTemplateRef = element.getAttribute("async-template"); - if (!StringUtils.hasText(asyncTemplateRef)) { - asyncTemplateRef = "asyncRabbitTemplate"; - } - builder.addConstructorArgReference(asyncTemplateRef); - } - else { - builder = BeanDefinitionBuilder.genericBeanDefinition(AmqpOutboundEndpoint.class); - String amqpTemplateRef = element.getAttribute("amqp-template"); - if (!StringUtils.hasText(amqpTemplateRef)) { - amqpTemplateRef = "amqpTemplate"; - } - builder.addConstructorArgReference(amqpTemplateRef); - builder.addPropertyValue("expectReply", true); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "exchange-name", true); - BeanDefinition exchangeNameExpression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("exchange-name-expression", element); - if (exchangeNameExpression != null) { - builder.addPropertyValue("exchangeNameExpression", exchangeNameExpression); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "routing-key", true); - BeanDefinition routingKeyExpression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("routing-key-expression", element); - if (routingKeyExpression != null) { - builder.addPropertyValue("routingKeyExpression", routingKeyExpression); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-delivery-mode"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "lazy-connect"); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "return-channel"); - BeanDefinition confirmCorrelationExpression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("confirm-correlation-expression", element); - if (confirmCorrelationExpression != null) { - builder.addPropertyValue("confirmCorrelationExpression", confirmCorrelationExpression); - } - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "confirm-ack-channel"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "confirm-nack-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "confirm-timeout"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-message-strategy"); - - BeanDefinitionBuilder mapperBuilder = BeanDefinitionBuilder - .genericBeanDefinition(DefaultAmqpHeaderMapper.class); - mapperBuilder.setFactoryMethod("outboundMapper"); - IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, mapperBuilder, - null); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "delay-expression", - "delayExpressionString"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "headers-last", "headersMappedLast"); - - return builder; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/package-info.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/package-info.java deleted file mode 100644 index 829ac211142..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for configuration - parsers, namespace handlers, factory beans. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.amqp.config; diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AbstractMessageListenerContainerSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AbstractMessageListenerContainerSpec.java deleted file mode 100644 index 9d2050487ce..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AbstractMessageListenerContainerSpec.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.Map; -import java.util.concurrent.Executor; - -import org.aopalliance.aop.Advice; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.MessagePostProcessor; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.support.MessagePropertiesConverter; -import org.springframework.amqp.support.ConditionalExceptionLogger; -import org.springframework.amqp.support.ConsumerTagStrategy; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.interceptor.TransactionAttribute; -import org.springframework.util.ErrorHandler; -import org.springframework.util.backoff.BackOff; - -/** - * Base class for container specs for containers that extend - * {@link AbstractMessageListenerContainer}. - * - * @param the current spec extension type - * @param the listener container type - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public abstract class AbstractMessageListenerContainerSpec, - C extends AbstractMessageListenerContainer> - extends MessageListenerContainerSpec { - - public AbstractMessageListenerContainerSpec(C listenerContainer) { - this.target = listenerContainer; - } - - @Override - public S id(String id) { - return super.id(id); - } - - /** - * @param acknowledgeMode the acknowledgeMode. - * @return the spec. - * @see AbstractMessageListenerContainer#setAcknowledgeMode(AcknowledgeMode) - */ - public S acknowledgeMode(AcknowledgeMode acknowledgeMode) { - this.target.setAcknowledgeMode(acknowledgeMode); - return _this(); - } - - /** - * @param queueName a vararg list of queue names to add. - * @return the spec. - * @see AbstractMessageListenerContainer#addQueueNames(String...) - */ - public S addQueueNames(String... queueName) { - this.target.addQueueNames(queueName); - return _this(); - } - - /** - * @param queues a vararg list of queues to add. - * @return the spec. - * @see AbstractMessageListenerContainer#addQueueNames(String...) - */ - public S addQueues(Queue... queues) { - this.target.addQueues(queues); - return _this(); - } - - /** - * @param errorHandler the errorHandler. - * @return the spec. - * @see AbstractMessageListenerContainer#setErrorHandler(ErrorHandler) - */ - public S errorHandler(ErrorHandler errorHandler) { - this.target.setErrorHandler(errorHandler); - return _this(); - } - - /** - * @param transactional true for transactional channels. - * @return the spec. - * @see AbstractMessageListenerContainer#setChannelTransacted(boolean) - */ - public S channelTransacted(boolean transactional) { - this.target.setChannelTransacted(transactional); - return _this(); - } - - /** - * @param adviceChain the adviceChain. - * @return the spec. - * @see AbstractMessageListenerContainer#setAdviceChain(Advice[]) - */ - public S adviceChain(Advice... adviceChain) { - this.target.setAdviceChain(adviceChain); - return _this(); - } - - /** - * @param recoveryInterval the recoveryInterval - * @return the spec. - * @see AbstractMessageListenerContainer#setRecoveryInterval(long) - */ - public S recoveryInterval(long recoveryInterval) { - this.target.setRecoveryInterval(recoveryInterval); - return _this(); - } - - /** - * @param exclusive true for exclusive. - * @return the spec. - * @see AbstractMessageListenerContainer#setExclusive(boolean) - */ - public S exclusive(boolean exclusive) { - this.target.setExclusive(exclusive); - return _this(); - } - - /** - * @param shutdownTimeout the shutdownTimeout. - * @return the spec. - * @see AbstractMessageListenerContainer#setShutdownTimeout(long) - */ - public S shutdownTimeout(long shutdownTimeout) { - this.target.setShutdownTimeout(shutdownTimeout); - return _this(); - } - - /** - * Configure an {@link Executor} used to invoke the message listener. - * @param taskExecutor the taskExecutor. - * @return the spec. - */ - public S taskExecutor(Executor taskExecutor) { - this.target.setTaskExecutor(taskExecutor); - return _this(); - } - - /** - * @param prefetchCount the prefetchCount. - * @return the spec. - * @see AbstractMessageListenerContainer#setPrefetchCount(int) - */ - public S prefetchCount(int prefetchCount) { - this.target.setPrefetchCount(prefetchCount); - return _this(); - } - - /** - * Configure a {@link PlatformTransactionManager}; used to synchronize the rabbit transaction - * with some other transaction(s). - * @param transactionManager the transactionManager. - * @return the spec. - */ - public S transactionManager(PlatformTransactionManager transactionManager) { - this.target.setTransactionManager(transactionManager); - return _this(); - } - - /** - * @param defaultRequeueRejected the defaultRequeueRejected. - * @return the spec. - * @see AbstractMessageListenerContainer#setDefaultRequeueRejected(boolean) - */ - public S defaultRequeueRejected(boolean defaultRequeueRejected) { - this.target.setDefaultRequeueRejected(defaultRequeueRejected); - return _this(); - } - - /** - * Determine whether the container should de-batch batched - * messages (true) or call the listener with the batch (false). Default: true. - * @param deBatchingEnabled the deBatchingEnabled to set. - * @return the spec. - * @see AbstractMessageListenerContainer#setDeBatchingEnabled(boolean) - */ - public S deBatchingEnabled(boolean deBatchingEnabled) { - this.target.setDeBatchingEnabled(deBatchingEnabled); - return _this(); - } - - /** - * Set {@link MessagePostProcessor}s that will be applied after message reception, before - * invoking the {@link org.springframework.amqp.core.MessageListener}. - * Often used to decompress data. Processors are invoked in order, - * depending on {@code PriorityOrder}, {@code Order} and finally unordered. - * @param afterReceivePostProcessors the post processor. - * @return the spec. - * @see AbstractMessageListenerContainer#setAfterReceivePostProcessors(MessagePostProcessor...) - */ - public S afterReceivePostProcessors(MessagePostProcessor... afterReceivePostProcessors) { - this.target.setAfterReceivePostProcessors(afterReceivePostProcessors); - return _this(); - } - - /** - * Set a qualifier that will prefix the connection factory lookup key; default none. - * @param lookupKeyQualifier the qualifier - * @return the spec. - * @see AbstractMessageListenerContainer#setLookupKeyQualifier(String) - */ - public S lookupKeyQualifier(String lookupKeyQualifier) { - this.target.setLookupKeyQualifier(lookupKeyQualifier); - return _this(); - } - - /** - * Set the implementation of {@link ConsumerTagStrategy} to generate consumer tags. - * By default, the RabbitMQ server generates consumer tags. - * @param consumerTagStrategy the consumerTagStrategy to set. - * @return the spec. - * @see AbstractMessageListenerContainer#setConsumerTagStrategy(ConsumerTagStrategy) - */ - public S consumerTagStrategy(ConsumerTagStrategy consumerTagStrategy) { - this.target.setConsumerTagStrategy(consumerTagStrategy); - return _this(); - } - - /** - * Set consumer arguments. - * @param args the arguments. - * @return the spec. - * @see AbstractMessageListenerContainer#setConsumerArguments(Map) - */ - public S consumerArguments(Map args) { - this.target.setConsumerArguments(args); - return _this(); - } - - /** - * How often to emit - * {@link org.springframework.amqp.rabbit.listener.ListenerContainerIdleEvent}s - * in milliseconds. - * @param idleEventInterval the interval. - * @return the spec. - * @see AbstractMessageListenerContainer#setIdleEventInterval(long) - */ - public S idleEventInterval(long idleEventInterval) { - this.target.setIdleEventInterval(idleEventInterval); - return _this(); - } - - /** - * Set the transaction attribute to use when using an external transaction manager. - * @param transactionAttribute the transaction attribute to set - * @return the spec. - * @see AbstractMessageListenerContainer#setTransactionAttribute(TransactionAttribute) - */ - public S transactionAttribute(TransactionAttribute transactionAttribute) { - this.target.setTransactionAttribute(transactionAttribute); - return _this(); - } - - /** - * Specify the {@link BackOff} for interval between recovery attempts. - * The default is 5000 ms, that is, 5 seconds. - * With the {@link BackOff} you can supply the {@code maxAttempts} for recovery before - * the {@code stop()} will be performed. - * @param recoveryBackOff The BackOff to recover. - * @return the spec. - * @see AbstractMessageListenerContainer#setRecoveryBackOff(BackOff) - */ - public S recoveryBackOff(BackOff recoveryBackOff) { - this.target.setRecoveryBackOff(recoveryBackOff); - return _this(); - } - - /** - * Set the {@link MessagePropertiesConverter} for this listener container. - * @param messagePropertiesConverter the converter for AMQP properties. - * @return the spec. - * @see AbstractMessageListenerContainer#setMessagePropertiesConverter(MessagePropertiesConverter) - */ - public S messagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) { - this.target.setMessagePropertiesConverter(messagePropertiesConverter); - return _this(); - } - - /** - * If all the configured queue(s) are not available on the broker, this setting - * determines whether the condition is fatal. When true, and - * the queues are missing during startup, the context refresh() will fail. - *

When false, the condition is not considered fatal and the container will - * continue to attempt to start the consumers. - * @param missingQueuesFatal the missingQueuesFatal to set. - * @return the spec. - * @see AbstractMessageListenerContainer#setMissingQueuesFatal(boolean) - */ - public S missingQueuesFatal(boolean missingQueuesFatal) { - this.target.setMissingQueuesFatal(missingQueuesFatal); - return _this(); - } - - /** - * Prevent the container from starting if any of the queues defined in the context have - * mismatched arguments (TTL etc.). Default false. - * @param mismatchedQueuesFatal true to fail initialization when this condition occurs. - * @return the spec. - * @see AbstractMessageListenerContainer#setMismatchedQueuesFatal(boolean) - */ - public S mismatchedQueuesFatal(boolean mismatchedQueuesFatal) { - this.target.setMismatchedQueuesFatal(mismatchedQueuesFatal); - return _this(); - } - - /** - * Set to true to automatically declare elements (queues, exchanges, bindings) - * in the application context during container start(). - * @param autoDeclare the boolean flag to indicate a declaration operation. - * @return the spec. - * @see AbstractMessageListenerContainer#setAutoDeclare(boolean) - */ - public S autoDeclare(boolean autoDeclare) { - this.target.setAutoDeclare(autoDeclare); - return _this(); - } - - /** - * Set the interval between passive queue declaration attempts in milliseconds. - * @param failedDeclarationRetryInterval the interval, default 5000. - * @return the spec. - * @see AbstractMessageListenerContainer#setFailedDeclarationRetryInterval(long) - */ - public S failedDeclarationRetryInterval(long failedDeclarationRetryInterval) { - this.target.setFailedDeclarationRetryInterval(failedDeclarationRetryInterval); - return _this(); - } - - /** - * Set whether a message with a null messageId is fatal for the consumer - * when using stateful retry. When false, instead of stopping the consumer, - * the message is rejected and not requeued - it will be discarded or routed - * to the dead letter queue, if so configured. Default true. - * @param statefulRetryFatalWithNullMessageId true for fatal. - * @return the spec. - * @see AbstractMessageListenerContainer#setStatefulRetryFatalWithNullMessageId(boolean) - */ - public S statefulRetryFatalWithNullMessageId(boolean statefulRetryFatalWithNullMessageId) { - this.target.setStatefulRetryFatalWithNullMessageId(statefulRetryFatalWithNullMessageId); - return _this(); - } - - /** - * Set a {@link ConditionalExceptionLogger} for logging exclusive consumer failures. The - * default is to log such failures at WARN level. - * @param exclusiveConsumerExceptionLogger the conditional exception logger. - * @return the spec. - * @see AbstractMessageListenerContainer#setExclusiveConsumerExceptionLogger(ConditionalExceptionLogger) - */ - public S exclusiveConsumerExceptionLogger(ConditionalExceptionLogger exclusiveConsumerExceptionLogger) { - this.target.setExclusiveConsumerExceptionLogger(exclusiveConsumerExceptionLogger); - return _this(); - } - - /** - * Set to true to always requeue on transaction rollback with an external TransactionManager. - * @param alwaysRequeueWithTxManagerRollback true to always requeue on rollback. - * @return the spec. - * @see AbstractMessageListenerContainer#setAlwaysRequeueWithTxManagerRollback(boolean) - */ - public S alwaysRequeueWithTxManagerRollback(boolean alwaysRequeueWithTxManagerRollback) { - this.target.setAlwaysRequeueWithTxManagerRollback(alwaysRequeueWithTxManagerRollback); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/Amqp.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/Amqp.java deleted file mode 100644 index 3ccc08a46a1..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/Amqp.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.AsyncRabbitTemplate; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.integration.amqp.channel.PollableAmqpChannel; -import org.springframework.integration.amqp.inbound.AmqpMessageSource.AmqpAckCallbackFactory; - -/** - * Factory class for AMQP components. - * - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -public final class Amqp { - - /** - * Create an initial {@link AmqpInboundGatewaySpec}. - * @param connectionFactory the connectionFactory. - * @param queueNames the queueNames. - * @return the AmqpInboundGatewaySpec. - */ - public static AmqpInboundGatewaySMLCSpec inboundGateway(ConnectionFactory connectionFactory, String... queueNames) { - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); - listenerContainer.setQueueNames(queueNames); - return inboundGateway(listenerContainer); - } - - /** - * Create an initial {@link AmqpInboundGatewaySpec}. - * @param connectionFactory the connectionFactory. - * @param amqpTemplate the {@link AmqpTemplate} to use. - * @param queueNames the queueNames. - * @return the AmqpInboundGatewaySpec. - */ - public static AmqpInboundGatewaySMLCSpec inboundGateway(ConnectionFactory connectionFactory, - AmqpTemplate amqpTemplate, String... queueNames) { - - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); - listenerContainer.setQueueNames(queueNames); - return inboundGateway(listenerContainer, amqpTemplate); - } - - /** - * Create an initial {@link AmqpInboundGatewaySpec}. - * @param connectionFactory the connectionFactory. - * @param queues the queues. - * @return the AmqpInboundGatewaySpec. - */ - public static AmqpInboundGatewaySMLCSpec inboundGateway(ConnectionFactory connectionFactory, Queue... queues) { - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); - listenerContainer.setQueues(queues); - return inboundGateway(listenerContainer); - } - - /** - * Create an initial {@link AmqpInboundGatewaySpec}. - * @param connectionFactory the connectionFactory. - * @param amqpTemplate the {@link AmqpTemplate} to use. - * @param queues the queues. - * @return the AmqpInboundGatewaySpec. - */ - public static AmqpInboundGatewaySMLCSpec inboundGateway(ConnectionFactory connectionFactory, - AmqpTemplate amqpTemplate, Queue... queues) { - - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); - listenerContainer.setQueues(queues); - return inboundGateway(listenerContainer, amqpTemplate); - } - - /** - * Create an initial {@link AmqpInboundGatewaySMLCSpec} - * with provided {@link SimpleMessageListenerContainer}. - * Note: only endpoint options are available from spec. - * The {@code listenerContainer} options should be specified - * on the provided {@link SimpleMessageListenerContainer} using - * {@link AmqpInboundGatewaySMLCSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer - * @return the AmqpInboundGatewaySMLCSpec. - */ - public static AmqpInboundGatewaySMLCSpec inboundGateway(SimpleMessageListenerContainer listenerContainer) { - return new AmqpInboundGatewaySMLCSpec(listenerContainer); - } - - /** - * Create an initial {@link AmqpInboundGatewaySMLCSpec} - * with provided {@link SimpleMessageListenerContainer}. - * Note: only endpoint options are available from spec. - * The {@code listenerContainer} options should be specified - * on the provided {@link SimpleMessageListenerContainer}using - * {@link AmqpInboundGatewaySMLCSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer - * @param amqpTemplate the {@link AmqpTemplate} to use. - * @return the AmqpInboundGatewaySMLCSpec. - */ - public static AmqpInboundGatewaySMLCSpec inboundGateway(SimpleMessageListenerContainer listenerContainer, - AmqpTemplate amqpTemplate) { - - return new AmqpInboundGatewaySMLCSpec(listenerContainer, amqpTemplate); - } - - /** - * Create an initial {@link DirectMessageListenerContainerSpec} - * with provided {@link DirectMessageListenerContainer}. - * Note: only endpoint options are available from spec. - * The {@code listenerContainer} options should be specified - * on the provided {@link DirectMessageListenerContainer} using - * {@link AmqpInboundGatewayDMLCSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer - * @return the AmqpInboundGatewayDMLCSpec. - */ - public static AmqpInboundGatewayDMLCSpec inboundGateway(DirectMessageListenerContainer listenerContainer) { - return new AmqpInboundGatewayDMLCSpec(listenerContainer); - } - - /** - * Create an initial {@link AmqpInboundGatewayDMLCSpec} - * with provided {@link DirectMessageListenerContainer}. - * Note: only endpoint options are available from spec. - * The {@code listenerContainer} options should be specified - * on the provided {@link DirectMessageListenerContainer} using - * {@link AmqpInboundGatewayDMLCSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer - * @param amqpTemplate the {@link AmqpTemplate} to use. - * @return the AmqpInboundGatewayDMLCSpec. - */ - public static AmqpInboundGatewayDMLCSpec inboundGateway(DirectMessageListenerContainer listenerContainer, - AmqpTemplate amqpTemplate) { - - return new AmqpInboundGatewayDMLCSpec(listenerContainer, amqpTemplate); - } - - /** - * Create an initial AmqpInboundPolledChannelAdapterSpec. - * @param connectionFactory the connectionFactory. - * @param queue the queue. - * @return the AmqpInboundPolledChannelAdapterSpec. - * @since 5.0.1 - */ - public static AmqpInboundPolledChannelAdapterSpec inboundPolledAdapter(ConnectionFactory connectionFactory, - String queue) { - - return new AmqpInboundPolledChannelAdapterSpec(connectionFactory, queue); - } - - /** - * Create an initial AmqpInboundPolledChannelAdapterSpec. - * @param connectionFactory the connectionFactory. - * @param ackCallbackFactory the ackCallbackFactory - * @param queue the queue. - * @return the AmqpInboundPolledChannelAdapterSpec. - * @since 5.0.1 - */ - public static AmqpInboundPolledChannelAdapterSpec inboundPolledAdapter(ConnectionFactory connectionFactory, - AmqpAckCallbackFactory ackCallbackFactory, String queue) { - - return new AmqpInboundPolledChannelAdapterSpec(connectionFactory, ackCallbackFactory, queue); - } - - /** - * Create an initial AmqpInboundChannelAdapterSpec using a - * {@link SimpleMessageListenerContainer}. - * @param connectionFactory the connectionFactory. - * @param queueNames the queueNames. - * @return the AmqpInboundChannelAdapterSpec. - */ - public static AmqpInboundChannelAdapterSMLCSpec inboundAdapter(ConnectionFactory connectionFactory, - String... queueNames) { - - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); - listenerContainer.setQueueNames(queueNames); - return new AmqpInboundChannelAdapterSMLCSpec(listenerContainer); - } - - /** - * Create an initial AmqpInboundChannelAdapterSpec using a - * {@link SimpleMessageListenerContainer}. - * @param connectionFactory the connectionFactory. - * @param queues the queues. - * @return the AmqpInboundChannelAdapterSpec. - */ - public static AmqpInboundChannelAdapterSMLCSpec inboundAdapter(ConnectionFactory connectionFactory, - Queue... queues) { - - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); - listenerContainer.setQueues(queues); - return new AmqpInboundChannelAdapterSMLCSpec(listenerContainer); - } - - /** - * Create an initial {@link AmqpInboundGatewaySMLCSpec} - * with provided {@link SimpleMessageListenerContainer}. - * Note: only endpoint options are available from spec. - * The {@code listenerContainer} options should be specified - * on the provided {@link SimpleMessageListenerContainer} using - * {@link AmqpInboundGatewaySMLCSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer - * @return the AmqpInboundGatewaySMLCSpec. - */ - public static AmqpInboundChannelAdapterSMLCSpec inboundAdapter(SimpleMessageListenerContainer listenerContainer) { - return new AmqpInboundChannelAdapterSMLCSpec(listenerContainer); - } - - /** - * Create an initial {@link AmqpInboundGatewayDMLCSpec} - * with provided {@link DirectMessageListenerContainer}. - * Note: only endpoint options are available from spec. - * The {@code listenerContainer} options should be specified - * on the provided {@link DirectMessageListenerContainer} using - * {@link AmqpInboundGatewaySMLCSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer - * @return the AmqpInboundGatewaySMLCSpec. - */ - public static AmqpInboundChannelAdapterDMLCSpec inboundAdapter(DirectMessageListenerContainer listenerContainer) { - return new AmqpInboundChannelAdapterDMLCSpec(listenerContainer); - } - - /** - * Create an initial AmqpOutboundEndpointSpec (adapter). - * @param amqpTemplate the amqpTemplate. - * @return the AmqpOutboundEndpointSpec. - */ - public static AmqpOutboundChannelAdapterSpec outboundAdapter(AmqpTemplate amqpTemplate) { - return new AmqpOutboundChannelAdapterSpec(amqpTemplate); - } - - /** - * Create an initial AmqpOutboundEndpointSpec (gateway). - * @param amqpTemplate the amqpTemplate. - * @return the AmqpOutboundEndpointSpec. - */ - public static AmqpOutboundGatewaySpec outboundGateway(AmqpTemplate amqpTemplate) { - return new AmqpOutboundGatewaySpec(amqpTemplate); - } - - /** - * Create an initial AmqpAsyncOutboundGatewaySpec. - * @param asyncRabbitTemplate the {@link AsyncRabbitTemplate}. - * @return the AmqpOutboundEndpointSpec. - */ - public static AmqpAsyncOutboundGatewaySpec asyncOutboundGateway(AsyncRabbitTemplate asyncRabbitTemplate) { - return new AmqpAsyncOutboundGatewaySpec(asyncRabbitTemplate); - } - - /** - * Create an initial AmqpPollableMessageChannelSpec. - * @param connectionFactory the connectionFactory. - * @return the AmqpPollableMessageChannelSpec. - */ - public static AmqpPollableMessageChannelSpec pollableChannel( - ConnectionFactory connectionFactory) { - - return new AmqpPollableMessageChannelSpec<>(connectionFactory); - } - - /** - * Create an initial AmqpPollableMessageChannelSpec. - * @param id the id. - * @param connectionFactory the connectionFactory. - * @return the AmqpPollableMessageChannelSpec. - */ - public static AmqpPollableMessageChannelSpec pollableChannel(String id, - ConnectionFactory connectionFactory) { - - AmqpPollableMessageChannelSpec spec = - new AmqpPollableMessageChannelSpec<>(connectionFactory); - return spec.id(id); - } - - /** - * Create an initial AmqpMessageChannelSpec. - * @param connectionFactory the connectionFactory. - * @return the AmqpMessageChannelSpec. - */ - public static AmqpMessageChannelSpec channel(ConnectionFactory connectionFactory) { - return new AmqpMessageChannelSpec<>(connectionFactory); - } - - /** - * Create an initial AmqpMessageChannelSpec. - * @param id the id. - * @param connectionFactory the connectionFactory. - * @return the AmqpMessageChannelSpec. - */ - public static AmqpMessageChannelSpec channel(String id, ConnectionFactory connectionFactory) { - return new AmqpMessageChannelSpec<>(connectionFactory) - .id(id); - } - - /** - * Create an initial AmqpPublishSubscribeMessageChannelSpec. - * @param connectionFactory the connectionFactory. - * @return the AmqpPublishSubscribeMessageChannelSpec. - */ - public static AmqpPublishSubscribeMessageChannelSpec publishSubscribeChannel(ConnectionFactory connectionFactory) { - return new AmqpPublishSubscribeMessageChannelSpec(connectionFactory); - } - - /** - * Create an initial AmqpPublishSubscribeMessageChannelSpec. - * @param id the id. - * @param connectionFactory the connectionFactory. - * @return the AmqpPublishSubscribeMessageChannelSpec. - */ - public static AmqpPublishSubscribeMessageChannelSpec publishSubscribeChannel(String id, - ConnectionFactory connectionFactory) { - - return new AmqpPublishSubscribeMessageChannelSpec(connectionFactory).id(id); - } - - private Amqp() { - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpAsyncOutboundGatewaySpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpAsyncOutboundGatewaySpec.java deleted file mode 100644 index 6970febf62b..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpAsyncOutboundGatewaySpec.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.AsyncRabbitTemplate; -import org.springframework.integration.amqp.outbound.AsyncAmqpOutboundGateway; - -/** - * @author Artem Bilan - * @since 5.0 - */ -public class AmqpAsyncOutboundGatewaySpec - extends AmqpBaseOutboundEndpointSpec { - - protected AmqpAsyncOutboundGatewaySpec(AsyncRabbitTemplate template) { - this.target = new AsyncAmqpOutboundGateway(template); - this.target.setRequiresReply(true); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseInboundChannelAdapterSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseInboundChannelAdapterSpec.java deleted file mode 100644 index ad93fc01d6c..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseInboundChannelAdapterSpec.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.retry.MessageRecoverer; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.core.retry.RetryTemplate; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.core.RecoveryCallback; -import org.springframework.integration.dsl.MessageProducerSpec; - -/** - * The base {@link MessageProducerSpec} implementation for a {@link AmqpInboundChannelAdapter}. - * - * @param the target {@link AmqpBaseInboundChannelAdapterSpec} implementation type. - * - * @author Artem Bilan - * - * @since 5.0 - */ -public class AmqpBaseInboundChannelAdapterSpec> - extends MessageProducerSpec { - - protected final DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); // NOSONAR - - protected AmqpBaseInboundChannelAdapterSpec(AmqpInboundChannelAdapter producer) { - super(producer); - this.target.setHeaderMapper(this.headerMapper); - } - - /** - * Configure the adapter's {@link MessageConverter}; - * defaults to {@link org.springframework.amqp.support.converter.SimpleMessageConverter}. - * @param messageConverter the messageConverter. - * @return the spec. - * @see AmqpInboundChannelAdapter#setMessageConverter - */ - public S messageConverter(MessageConverter messageConverter) { - this.target.setMessageConverter(messageConverter); - return _this(); - } - - /** - * Configure the adapter's {@link AmqpHeaderMapper}; - * defaults to {@link DefaultAmqpHeaderMapper}. - * @param headerMapper the headerMapper. - * @return the spec. - */ - public S headerMapper(AmqpHeaderMapper headerMapper) { - this.target.setHeaderMapper(headerMapper); - return _this(); - } - - /** - * Only applies if the default header mapper is used. - * @param headers the headers. - * @return the spec. - * @see DefaultAmqpHeaderMapper#setRequestHeaderNames(String[]) - */ - public S mappedRequestHeaders(String... headers) { - this.headerMapper.setRequestHeaderNames(headers); - return _this(); - } - - /** - * Set a {@link RetryTemplate} to use for retrying a message delivery within the - * adapter. - * @param retryTemplate the template. - * @return the spec. - * @since 5.0.2 - * @see AmqpInboundChannelAdapter#setRetryTemplate(RetryTemplate) - */ - public S retryTemplate(RetryTemplate retryTemplate) { - this.target.setRetryTemplate(retryTemplate); - return _this(); - } - - /** - * Set a {@link RecoveryCallback} when using retry within the adapter. - * @param recoveryCallback the callback. - * @return the spec. - * @since 5.0.2 - * @see AmqpInboundChannelAdapter#setRecoveryCallback(RecoveryCallback) - */ - public S recoveryCallback(RecoveryCallback recoveryCallback) { - this.target.setRecoveryCallback(recoveryCallback); - return _this(); - } - - /** - * Set a {@link MessageRecoverer} when using retry within the adapter. - * @param messageRecoverer the callback. - * @return the spec. - * @since 5.5 - * @see AmqpInboundChannelAdapter#setMessageRecoverer(MessageRecoverer) - */ - public S messageRecoverer(MessageRecoverer messageRecoverer) { - this.target.setMessageRecoverer(messageRecoverer); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseInboundGatewaySpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseInboundGatewaySpec.java deleted file mode 100644 index e50f4b76893..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseInboundGatewaySpec.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.batch.BatchingStrategy; -import org.springframework.amqp.rabbit.retry.MessageRecoverer; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.core.retry.RetryTemplate; -import org.springframework.integration.amqp.inbound.AmqpInboundGateway; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.core.RecoveryCallback; -import org.springframework.integration.dsl.MessagingGatewaySpec; - -/** - * A base {@link MessagingGatewaySpec} implementation for {@link AmqpInboundGateway} endpoint options. - * Doesn't allow to specify {@code listenerContainer} options. - * - * @param the target {@link AmqpBaseInboundGatewaySpec} implementation type. - * - * @author Artem Bilan - * - * @since 5.0 - * - * @see AmqpInboundGateway - */ -public class AmqpBaseInboundGatewaySpec> - extends MessagingGatewaySpec { - - protected final DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); // NOSONAR - - protected AmqpBaseInboundGatewaySpec(AmqpInboundGateway gateway) { - super(gateway); - this.target.setHeaderMapper(this.headerMapper); - } - - /** - * Configure the gateway's {@link MessageConverter}; - * defaults to {@link org.springframework.amqp.support.converter.SimpleMessageConverter}. - * @param messageConverter the messageConverter. - * @return the spec. - * @see AmqpInboundGateway#setMessageConverter - */ - public S messageConverter(MessageConverter messageConverter) { - this.target.setMessageConverter(messageConverter); - return _this(); - } - - /** - * Configure the gateway's {@link AmqpHeaderMapper}; defaults to - * {@link org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper}. - * @param headerMapper the headerMapper. - * @return the spec. - * @see AmqpInboundGateway#setHeaderMapper(AmqpHeaderMapper) - */ - public S headerMapper(AmqpHeaderMapper headerMapper) { - this.target.setHeaderMapper(headerMapper); - return _this(); - } - - /** - * Only applies if the default header mapper is used. - * @param headers the headers. - * @return the spec. - * @see DefaultAmqpHeaderMapper#setRequestHeaderNames(String[]) - */ - public S mappedRequestHeaders(String... headers) { - this.headerMapper.setRequestHeaderNames(headers); - return _this(); - } - - /** - * Only applies if the default header mapper is used. - * @param headers the headers. - * @return the spec. - * @see DefaultAmqpHeaderMapper#setReplyHeaderNames(String[]) - */ - public S mappedReplyHeaders(String... headers) { - this.headerMapper.setReplyHeaderNames(headers); - return _this(); - } - - /** - * The {@code defaultReplyTo} address with the form - *

-	 * (exchange)/(routingKey)
-	 * 
- * or - *
-	 * (queueName)
-	 * 
- * if the request message doesn't have a {@code replyTo} property. - * The second form uses the default exchange ("") and the queue name as - * the routing key. - * @param defaultReplyTo the default {@code replyTo} address to use. - * @return the spec. - * @see AmqpInboundGateway#setDefaultReplyTo - */ - public S defaultReplyTo(String defaultReplyTo) { - this.target.setDefaultReplyTo(defaultReplyTo); - return _this(); - } - - /** - * Set a {@link RetryTemplate} to use for retrying a message delivery within the - * adapter. - * @param retryTemplate the template. - * @return the spec. - * @since 5.0.2 - * @see AmqpInboundGateway#setRetryTemplate(RetryTemplate) - */ - public S retryTemplate(RetryTemplate retryTemplate) { - this.target.setRetryTemplate(retryTemplate); - return _this(); - } - - /** - * Set a {@link RecoveryCallback} when using retry within the adapter. - * @param recoveryCallback the callback. - * @return the spec. - * @since 5.0.2 - * @see AmqpInboundGateway#setRecoveryCallback(RecoveryCallback) - */ - public S recoveryCallback(RecoveryCallback recoveryCallback) { - this.target.setRecoveryCallback(recoveryCallback); - return _this(); - } - - /** - * Set a batching strategy to use when de-batching messages. - * @param batchingStrategy the strategy to use. - * @return the spec. - * @since 5.2.1 - * @see AmqpInboundGateway#setBatchingStrategy(BatchingStrategy) - */ - public S batchingStrategy(BatchingStrategy batchingStrategy) { - this.target.setBatchingStrategy(batchingStrategy); - return _this(); - } - - /** - * Set to true to bind the source message in the headers. - * @param bindSourceMessage true to bind. - * @return the spec. - * @since 5.1.9 - * @see AmqpInboundGateway#setBindSourceMessage(boolean) - */ - public S bindSourceMessage(boolean bindSourceMessage) { - this.target.setBindSourceMessage(bindSourceMessage); - return _this(); - } - - /** - * When mapping headers for the outbound (reply) message, determine whether the headers are - * mapped before the message is converted, or afterwards. - * @param replyHeadersMappedLast true if reply headers are mapped after conversion. - * @return the spec. - * @since 5.1.9 - * @see AmqpInboundGateway#setReplyHeadersMappedLast(boolean) - */ - public S replyHeadersMappedLast(boolean replyHeadersMappedLast) { - this.target.setReplyHeadersMappedLast(replyHeadersMappedLast); - return _this(); - } - - /** - * Set a {@link MessageRecoverer} when using retry within the adapter. - * @param messageRecoverer the callback. - * @return the spec. - * @since 5.5 - * @see org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter#setMessageRecoverer(MessageRecoverer) - */ - public S messageRecoverer(MessageRecoverer messageRecoverer) { - this.target.setMessageRecoverer(messageRecoverer); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseOutboundEndpointSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseOutboundEndpointSpec.java deleted file mode 100644 index a5d15963033..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpBaseOutboundEndpointSpec.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Function; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.expression.Expression; -import org.springframework.integration.amqp.outbound.AbstractAmqpOutboundEndpoint; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.dsl.MessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.support.ErrorMessageStrategy; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; - -/** - * The base {@link MessageHandlerSpec} for {@link AbstractAmqpOutboundEndpoint}s. - * - * @param the target {@link AmqpBaseOutboundEndpointSpec} implementation type. - * @param the target {@link AbstractAmqpOutboundEndpoint} implementation type. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 5.0 - */ -public abstract class -AmqpBaseOutboundEndpointSpec, E extends AbstractAmqpOutboundEndpoint> - extends MessageHandlerSpec { - - protected final DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); // NOSONAR final - - /** - * Set a custom {@link AmqpHeaderMapper} for mapping request and reply headers. - * @param headerMapper the {@link AmqpHeaderMapper} to use. - * @return the spec - */ - public S headerMapper(AmqpHeaderMapper headerMapper) { - this.target.setHeaderMapper(headerMapper); - return _this(); - } - - /** - * Set the default delivery mode. - * @param defaultDeliveryMode the delivery mode. - * @return the spec - */ - public S defaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) { - this.target.setDefaultDeliveryMode(defaultDeliveryMode); - return _this(); - } - - /** - * Configure an AMQP routing key for sending messages. - * @param routingKey the routing key to use - * @return the spec - */ - public S routingKey(String routingKey) { - this.target.setRoutingKey(routingKey); - return _this(); - } - - /** - * A SpEL expression to evaluate routing key at runtime. - * @param routingKeyExpression the expression to use. - * @return the spec - */ - public S routingKeyExpression(String routingKeyExpression) { - return routingKeyExpression(PARSER.parseExpression(routingKeyExpression)); - } - - /** - * A function to evaluate routing key at runtime. - * @param routingKeyFunction the {@link Function} to use. - * @return the spec - */ - public S routingKeyFunction(Function, String> routingKeyFunction) { - return routingKeyExpression(new FunctionExpression<>(routingKeyFunction)); - } - - /** - * A SpEL expression to evaluate routing key at runtime. - * @param routingKeyExpression the expression to use. - * @return the spec - */ - public S routingKeyExpression(Expression routingKeyExpression) { - this.target.setRoutingKeyExpression(routingKeyExpression); - return _this(); - } - - /** - * Set the channel to which returned messages are sent. - * @param returnChannel the channel. - * @return the spec - */ - public S returnChannel(MessageChannel returnChannel) { - this.target.setReturnChannel(returnChannel); - return _this(); - } - - /** - * Set the channel to which acks are send (publisher confirms). - * @param ackChannel the channel. - * @return the spec - */ - public S confirmAckChannel(MessageChannel ackChannel) { - this.target.setConfirmAckChannel(ackChannel); - return _this(); - } - - /** - * Configure an AMQP exchange name for sending messages. - * @param exchangeName the exchange name for sending messages. - * @return the spec - */ - public S exchangeName(String exchangeName) { - this.target.setExchangeName(exchangeName); - return _this(); - } - - /** - * Configure a SpEL expression to evaluate an exchange name at runtime. - * @param exchangeNameExpression the expression to use. - * @return the spec - */ - public S exchangeNameExpression(String exchangeNameExpression) { - return exchangeNameExpression(PARSER.parseExpression(exchangeNameExpression)); - } - - /** - * Configure a {@link Function} to evaluate an exchange name at runtime. - * @param exchangeNameFunction the function to use. - * @return the spec - */ - public S exchangeNameFunction(Function, String> exchangeNameFunction) { - return exchangeNameExpression(new FunctionExpression<>(exchangeNameFunction)); - } - - /** - * Configure a SpEL expression to evaluate an exchange name at runtime. - * @param exchangeNameExpression the expression to use. - * @return the spec - */ - public S exchangeNameExpression(Expression exchangeNameExpression) { - this.target.setExchangeNameExpression(exchangeNameExpression); - return _this(); - } - - /** - * Set the channel to which nacks are send (publisher confirms). - * @param nackChannel the channel. - * @return the spec - */ - public S confirmNackChannel(MessageChannel nackChannel) { - this.target.setConfirmNackChannel(nackChannel); - return _this(); - } - - /** - * Set a SpEL expression to evaluate confirm correlation at runtime. - * @param confirmCorrelationExpression the expression to use. - * @return the spec - */ - public S confirmCorrelationExpression(String confirmCorrelationExpression) { - return confirmCorrelationExpression(PARSER.parseExpression(confirmCorrelationExpression)); - } - - /** - * Set a {@link Function} to evaluate confirm correlation at runtime. - * @param confirmCorrelationFunction the function to use. - * @return the spec - */ - public S confirmCorrelationFunction(Function, Object> confirmCorrelationFunction) { - return confirmCorrelationExpression(new FunctionExpression<>(confirmCorrelationFunction)); - } - - /** - * Set a SpEL expression to evaluate confirm correlation at runtime. - * @param confirmCorrelationExpression the expression to use. - * @return the spec - */ - public S confirmCorrelationExpression(Expression confirmCorrelationExpression) { - this.target.setConfirmCorrelationExpression(confirmCorrelationExpression); - return _this(); - } - - /** - * Provide the header names that should be mapped from a request to a - * {@link org.springframework.messaging.MessageHeaders}. - * @param headers The request header names. - * @return the spec - */ - public S mappedRequestHeaders(String... headers) { - this.headerMapper.setRequestHeaderNames(headers); - return _this(); - } - - /** - * Provide the header names that should be mapped to a response - * from a {@link org.springframework.messaging.MessageHeaders}. - * @param headers The reply header names. - * @return the spec - */ - public S mappedReplyHeaders(String... headers) { - this.headerMapper.setReplyHeaderNames(headers); - return _this(); - } - - /** - * Determine whether the headers are - * mapped before the message is converted, or afterwards. - * @param headersLast true to map headers last. - * @return the spec. - * @see AbstractAmqpOutboundEndpoint#setHeadersMappedLast(boolean) - */ - public S headersMappedLast(boolean headersLast) { - this.target.setHeadersMappedLast(headersLast); - return _this(); - } - - /** - * Set to {@code false} to attempt to connect during endpoint start. - * @param lazyConnect the lazyConnect to set. - * @return the spec. - * @since 5.0.2 - * @see AbstractAmqpOutboundEndpoint#setLazyConnect(boolean) - */ - public S lazyConnect(boolean lazyConnect) { - this.target.setLazyConnect(lazyConnect); - return _this(); - } - - /** - * Set the value to set in the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. - * @param delay the delay. - * @return the spec. - * @since 5.0.2 - * @see AbstractAmqpOutboundEndpoint#setDelay(int) - */ - public S delay(int delay) { - this.target.setDelay(delay); - return _this(); - } - - /** - * Set the function to calculate the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. - * @param delayFunction the function to evaluate the value for the {@code x-delay} header. - * @return the spec. - * @since 5.0.2 - * @see #delayExpression(Expression) - */ - public S delayFunction(Function, Integer> delayFunction) { - return delayExpression(new FunctionExpression<>(delayFunction)); - } - - /** - * Set the SpEL expression to calculate the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. - * @param delayExpression the expression. - * @return the spec. - * @since 5.0.2 - * @see AbstractAmqpOutboundEndpoint#setDelayExpression(Expression) - */ - public S delayExpression(Expression delayExpression) { - this.target.setDelayExpression(delayExpression); - return _this(); - } - - /** - * Set the SpEL expression to calculate the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. - * @param delayExpression the expression. - * @return the spec. - * @since 5.0.2 - * @see AbstractAmqpOutboundEndpoint#setDelayExpressionString(String) - */ - public S delayExpression(String delayExpression) { - this.target.setDelayExpressionString(delayExpression); - return _this(); - } - - /** - * Set the error message strategy to use for returned (or negatively confirmed) - * messages. - * @param errorMessageStrategy the strategy. - * @return the spec. - * @since 5.0.2 - * @see AbstractAmqpOutboundEndpoint#setErrorMessageStrategy(ErrorMessageStrategy) - */ - public S errorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) { - this.target.setErrorMessageStrategy(errorMessageStrategy); - return _this(); - } - - /** - * Set a timeout after which a nack will be synthesized if no publisher confirm has - * been received within that time. Missing confirms will be checked every 50% of this - * value so the synthesized nack will be sent between 1x and 1.5x this timeout. - * @param timeout the approximate timeout. - * @return the spec. - * @since 5.3 - */ - public S confirmTimeout(long timeout) { - this.target.setConfirmTimeout(timeout); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterDMLCSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterDMLCSpec.java deleted file mode 100644 index 7cb06a9d392..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterDMLCSpec.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Consumer; - -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; - -/** - * Spec for an inbound channel adapter with a {@link DirectMessageListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public class AmqpInboundChannelAdapterDMLCSpec - extends AmqpInboundChannelAdapterSpec { - - protected AmqpInboundChannelAdapterDMLCSpec(DirectMessageListenerContainer listenerContainer) { - super(new DirectMessageListenerContainerSpec(listenerContainer)); - } - - public AmqpInboundChannelAdapterDMLCSpec configureContainer( - Consumer configurer) { - - configurer.accept((DirectMessageListenerContainerSpec) this.listenerContainerSpec); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSMLCSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSMLCSpec.java deleted file mode 100644 index f074ddd5b11..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSMLCSpec.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Consumer; - -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter.BatchMode; - -/** - * Spec for an inbound channel adapter with a {@link SimpleMessageListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public class AmqpInboundChannelAdapterSMLCSpec - extends AmqpInboundChannelAdapterSpec { - - protected AmqpInboundChannelAdapterSMLCSpec(SimpleMessageListenerContainer listenerContainer) { - super(new SimpleMessageListenerContainerSpec(listenerContainer)); - } - - public AmqpInboundChannelAdapterSMLCSpec configureContainer( - Consumer configurer) { - - configurer.accept((SimpleMessageListenerContainerSpec) this.listenerContainerSpec); - return this; - } - - /** - * Set the {@link BatchMode} to use when the container is configured to support - * batching consumed records. - * @param batchMode the batch mode. - * @return the spec. - * @since 5.3 - */ - public AmqpInboundChannelAdapterSMLCSpec batchMode(BatchMode batchMode) { - this.target.setBatchMode(batchMode); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSpec.java deleted file mode 100644 index f8763629b66..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSpec.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.Collections; -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.rabbit.listener.MessageListenerContainer; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter; -import org.springframework.integration.dsl.ComponentsRegistration; - -/** - * A {@link org.springframework.integration.dsl.MessageProducerSpec} for - * {@link AmqpInboundChannelAdapter}s. - * - * @param the spec type. - * @param the container type. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 5.0 - */ -public abstract class AmqpInboundChannelAdapterSpec - , C extends MessageListenerContainer> - extends AmqpBaseInboundChannelAdapterSpec - implements ComponentsRegistration { - - protected final MessageListenerContainerSpec listenerContainerSpec; // NOSONAR final - - protected AmqpInboundChannelAdapterSpec(MessageListenerContainerSpec listenerContainerSpec) { - super(new AmqpInboundChannelAdapter(listenerContainerSpec.getObject())); - this.listenerContainerSpec = listenerContainerSpec; - } - - @Override - public Map getComponentsToRegister() { - return Collections.singletonMap( - this.listenerContainerSpec.getObject(), this.listenerContainerSpec.getId()); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewayDMLCSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewayDMLCSpec.java deleted file mode 100644 index a8947a78abe..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewayDMLCSpec.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Consumer; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; - -/** - * Spec for a gateway with a {@link DirectMessageListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public class AmqpInboundGatewayDMLCSpec - extends AmqpInboundGatewaySpec { - - protected AmqpInboundGatewayDMLCSpec(DirectMessageListenerContainer listenerContainer, AmqpTemplate amqpTemplate) { - super(new DirectMessageListenerContainerSpec(listenerContainer), amqpTemplate); - } - - protected AmqpInboundGatewayDMLCSpec(DirectMessageListenerContainer listenerContainer) { - super(new DirectMessageListenerContainerSpec(listenerContainer)); - } - - public AmqpInboundGatewayDMLCSpec configureContainer(Consumer configurer) { - configurer.accept((DirectMessageListenerContainerSpec) this.listenerContainerSpec); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySMLCSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySMLCSpec.java deleted file mode 100644 index 4a050c9a1de..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySMLCSpec.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Consumer; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; - -/** - * Spec for a gateway with a {@link SimpleMessageListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public class AmqpInboundGatewaySMLCSpec - extends AmqpInboundGatewaySpec { - - protected AmqpInboundGatewaySMLCSpec(SimpleMessageListenerContainer listenerContainer, AmqpTemplate amqpTemplate) { - super(new SimpleMessageListenerContainerSpec(listenerContainer), amqpTemplate); - } - - protected AmqpInboundGatewaySMLCSpec(SimpleMessageListenerContainer listenerContainer) { - super(new SimpleMessageListenerContainerSpec(listenerContainer)); - } - - public AmqpInboundGatewaySMLCSpec configureContainer(Consumer configurer) { - configurer.accept((SimpleMessageListenerContainerSpec) this.listenerContainerSpec); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySpec.java deleted file mode 100644 index d1b8feb2112..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySpec.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.Collections; -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.integration.amqp.inbound.AmqpInboundGateway; -import org.springframework.integration.dsl.ComponentsRegistration; - -/** - * An {@link AmqpBaseInboundGatewaySpec} implementation for a {@link AmqpInboundGateway}. - * Allows to provide {@link AbstractMessageListenerContainer} options. - * - * @param the spec type. - * @param the container type. - * - * @author Artem Bilan - * - * @since 5.0 - */ -public abstract class AmqpInboundGatewaySpec - , C extends AbstractMessageListenerContainer> - extends AmqpBaseInboundGatewaySpec - implements ComponentsRegistration { - - protected final AbstractMessageListenerContainerSpec listenerContainerSpec; // NOSONAR final - - protected AmqpInboundGatewaySpec(AbstractMessageListenerContainerSpec listenerContainerSpec) { - super(new AmqpInboundGateway(listenerContainerSpec.getObject())); - this.listenerContainerSpec = listenerContainerSpec; - } - - /** - * Instantiate {@link AmqpInboundGateway} based on the provided {@link AbstractMessageListenerContainer} - * and {@link AmqpTemplate}. - * @param listenerContainerSpec the {@link AbstractMessageListenerContainerSpec} to use. - * @param amqpTemplate the {@link AmqpTemplate} to use. - */ - AmqpInboundGatewaySpec(AbstractMessageListenerContainerSpec listenerContainerSpec, - AmqpTemplate amqpTemplate) { - - super(new AmqpInboundGateway(listenerContainerSpec.getObject(), amqpTemplate)); - this.listenerContainerSpec = listenerContainerSpec; - } - - @Override - public Map getComponentsToRegister() { - return Collections.singletonMap( - this.listenerContainerSpec.getObject(), this.listenerContainerSpec.getId()); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundPolledChannelAdapterSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundPolledChannelAdapterSpec.java deleted file mode 100644 index 3327e290d8a..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundPolledChannelAdapterSpec.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.support.MessagePropertiesConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.integration.amqp.inbound.AmqpMessageSource; -import org.springframework.integration.amqp.inbound.AmqpMessageSource.AmqpAckCallbackFactory; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.dsl.MessageSourceSpec; - -/** - * Spec for a polled AMQP inbound channel adapter. - * - * @author Gary Russell - * - * @since 5.0.1 - * - */ -public class AmqpInboundPolledChannelAdapterSpec - extends MessageSourceSpec { - - protected AmqpInboundPolledChannelAdapterSpec(ConnectionFactory connectionFactory, String queue) { - this.target = new AmqpMessageSource(connectionFactory, queue); - } - - protected AmqpInboundPolledChannelAdapterSpec(ConnectionFactory connectionFactory, - AmqpAckCallbackFactory ackCallbackFactory, String queue) { - - this.target = new AmqpMessageSource(connectionFactory, ackCallbackFactory, queue); - } - - public AmqpInboundPolledChannelAdapterSpec transacted(boolean transacted) { - this.target.setTransacted(transacted); - return this; - } - - public AmqpInboundPolledChannelAdapterSpec propertiesConverter(MessagePropertiesConverter propertiesConverter) { - this.target.setPropertiesConverter(propertiesConverter); - return this; - } - - public AmqpInboundPolledChannelAdapterSpec headerMapper(AmqpHeaderMapper headerMapper) { - this.target.setHeaderMapper(headerMapper); - return this; - } - - public AmqpInboundPolledChannelAdapterSpec messageConverter(MessageConverter messageConverter) { - this.target.setMessageConverter(messageConverter); - return this; - } - - public AmqpInboundPolledChannelAdapterSpec rawMessageHeader(boolean rawMessageHeader) { - this.target.setRawMessageHeader(rawMessageHeader); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpMessageChannelSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpMessageChannelSpec.java deleted file mode 100644 index 74a23d8891f..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpMessageChannelSpec.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.Executor; - -import org.aopalliance.aop.Advice; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.integration.amqp.channel.AbstractAmqpChannel; -import org.springframework.integration.amqp.config.AmqpChannelFactoryBean; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.interceptor.TransactionAttribute; -import org.springframework.util.ErrorHandler; - -/** - * An {@link AmqpPollableMessageChannelSpec} for a message-driven - * {@link org.springframework.integration.amqp.channel.PointToPointSubscribableAmqpChannel}. - * - * @param the target {@link AmqpMessageChannelSpec} implementation type. - * @param the target channel type. - * - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -public class AmqpMessageChannelSpec, T extends AbstractAmqpChannel> - extends AmqpPollableMessageChannelSpec { - - protected final List adviceChain = new LinkedList<>(); // NOSONAR - - protected AmqpMessageChannelSpec(ConnectionFactory connectionFactory) { - super(new AmqpChannelFactoryBean(true), connectionFactory); - } - - /** - * @param maxSubscribers the maxSubscribers. - * @return the spec. - * @see org.springframework.integration.amqp.channel.PointToPointSubscribableAmqpChannel#setMaxSubscribers(int) - */ - public S maxSubscribers(int maxSubscribers) { - this.amqpChannelFactoryBean.setMaxSubscribers(maxSubscribers); - return _this(); - } - - /** - * @param acknowledgeMode the acknowledgeMode. - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setAcknowledgeMode(AcknowledgeMode) - */ - public S acknowledgeMode(AcknowledgeMode acknowledgeMode) { - this.amqpChannelFactoryBean.setAcknowledgeMode(acknowledgeMode); - return _this(); - } - - /** - * @param advice the advice. - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setAdviceChain(Advice[]) - */ - public S advice(Advice... advice) { - this.adviceChain.addAll(Arrays.asList(advice)); - return _this(); - } - - /** - * @param autoStartup the autoStartup. - * @return the spec. - * @see org.springframework.context.SmartLifecycle - */ - public S autoStartup(boolean autoStartup) { - this.amqpChannelFactoryBean.setAutoStartup(autoStartup); - return _this(); - } - - /** - * @param concurrentConsumers the concurrentConsumers - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setConcurrentConsumers(int) - */ - public S concurrentConsumers(int concurrentConsumers) { - this.amqpChannelFactoryBean.setConcurrentConsumers(concurrentConsumers); - return _this(); - } - - /** - * @param errorHandler the errorHandler. - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setErrorHandler(ErrorHandler) - */ - public S errorHandler(ErrorHandler errorHandler) { - this.amqpChannelFactoryBean.setErrorHandler(errorHandler); - return _this(); - } - - /** - * @param exposeListenerChannel the exposeListenerChannel. - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setExposeListenerChannel(boolean) - */ - public S exposeListenerChannel(boolean exposeListenerChannel) { - this.amqpChannelFactoryBean.setExposeListenerChannel(exposeListenerChannel); - return _this(); - } - - /** - * @param phase the phase. - * @return the spec. - * @see org.springframework.context.SmartLifecycle - */ - public S phase(int phase) { - this.amqpChannelFactoryBean.setPhase(phase); - return _this(); - } - - /** - * @param prefetchCount the prefetchCount. - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setPrefetchCount(int) - */ - public S prefetchCount(int prefetchCount) { - this.amqpChannelFactoryBean.setPrefetchCount(prefetchCount); - return _this(); - } - - /** - * @param receiveTimeout the receiveTimeout - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setReceiveTimeout(long) - */ - public S receiveTimeout(long receiveTimeout) { - this.amqpChannelFactoryBean.setReceiveTimeout(receiveTimeout); - return _this(); - } - - /** - * @param recoveryInterval the recoveryInterval - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setRecoveryInterval(long) - */ - public S recoveryInterval(long recoveryInterval) { - this.amqpChannelFactoryBean.setRecoveryInterval(recoveryInterval); - return _this(); - } - - /** - * @param shutdownTimeout the shutdownTimeout. - * @return the spec. - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setShutdownTimeout(long) - */ - public S shutdownTimeout(long shutdownTimeout) { - this.amqpChannelFactoryBean.setShutdownTimeout(shutdownTimeout); - return _this(); - } - - /** - * Configure an {@link Executor} used to invoke the message listener. - * @param taskExecutor the taskExecutor. - * @return the spec. - */ - public S taskExecutor(Executor taskExecutor) { - this.amqpChannelFactoryBean.setTaskExecutor(taskExecutor); - return _this(); - } - - /** - * Configure a {@link TransactionAttribute} to be used with the - * {@link #transactionManager(PlatformTransactionManager)}. - * @param transactionAttribute the transactionAttribute. - * @return the spec. - */ - public S transactionAttribute(TransactionAttribute transactionAttribute) { - this.amqpChannelFactoryBean.setTransactionAttribute(transactionAttribute); - return _this(); - } - - /** - * Configure a {@link PlatformTransactionManager}; used to synchronize the rabbit transaction - * with some other transaction(s). - * @param transactionManager the transactionManager. - * @return the spec. - */ - public S transactionManager(PlatformTransactionManager transactionManager) { - this.amqpChannelFactoryBean.setTransactionManager(transactionManager); - return _this(); - } - - /** - * Configure the batch size. - * @param batchSize the batchSize. - * @return the spec. - * @since 5.2 - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setBatchSize(int) - */ - public S batchSize(int batchSize) { - this.amqpChannelFactoryBean.setBatchSize(batchSize); - return _this(); - } - - @Override - protected T doGet() { - this.amqpChannelFactoryBean.setAdviceChain(this.adviceChain.toArray(new Advice[0])); - return super.doGet(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundChannelAdapterSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundChannelAdapterSpec.java deleted file mode 100644 index 957e96bb397..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundChannelAdapterSpec.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.core.AmqpTemplate; - -/** - * Spec for an outbound AMQP channel adapter. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.3 - * - */ -public class AmqpOutboundChannelAdapterSpec extends AmqpOutboundEndpointSpec { - - protected AmqpOutboundChannelAdapterSpec(AmqpTemplate amqpTemplate) { - super(amqpTemplate, false); - } - - /** - * If true, and the message payload is an {@link Iterable} of {@link org.springframework.messaging.Message}, - * send the messages in a single invocation of the template (same channel) and optionally - * wait for the confirms or die. - * @param multiSend true to send multiple messages. - * @return the spec. - */ - public AmqpOutboundChannelAdapterSpec multiSend(boolean multiSend) { - this.target.setMultiSend(multiSend); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundEndpointSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundEndpointSpec.java deleted file mode 100644 index ec74a5ce9ad..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundEndpointSpec.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; -import org.springframework.util.Assert; - -/** - * Base spec for outbound AMQP endpoints. - * - * @param the spec subclass type. - * - * @author Artem Bilan - * @since 5.0 - */ -public abstract class AmqpOutboundEndpointSpec> - extends AmqpBaseOutboundEndpointSpec { - - protected final boolean expectReply; // NOSONAR - - protected AmqpOutboundEndpointSpec(AmqpTemplate amqpTemplate, boolean expectReply) { - this.expectReply = expectReply; - this.target = new AmqpOutboundEndpoint(amqpTemplate); - this.target.setExpectReply(expectReply); - this.target.setHeaderMapper(this.headerMapper); - if (expectReply) { - this.target.setRequiresReply(true); - } - } - - @Override - public S mappedReplyHeaders(String... headers) { - Assert.isTrue(this.expectReply, "'mappedReplyHeaders' can be applied only for gateway"); - return super.mappedReplyHeaders(headers); - } - - /** - * Wait for a publisher confirm. - * @param waitForConfirm true to wait. - * @return the spec. - * @since 5.2 - * @see AmqpOutboundEndpoint#setWaitForConfirm(boolean) - */ - public S waitForConfirm(boolean waitForConfirm) { - this.target.setWaitForConfirm(waitForConfirm); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundGatewaySpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundGatewaySpec.java deleted file mode 100644 index 5f95c7be62f..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpOutboundGatewaySpec.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.core.AmqpTemplate; - -/** - * Spec for an outbound AMQP gateway. - * - * @author Gary Russell - * @since 5.3 - * - */ -public class AmqpOutboundGatewaySpec extends AmqpOutboundEndpointSpec { - - protected AmqpOutboundGatewaySpec(AmqpTemplate amqpTemplate) { - super(amqpTemplate, true); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpPollableMessageChannelSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpPollableMessageChannelSpec.java deleted file mode 100644 index 54fd2dc1ac8..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpPollableMessageChannelSpec.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.support.MessagePropertiesConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.integration.amqp.channel.AbstractAmqpChannel; -import org.springframework.integration.amqp.config.AmqpChannelFactoryBean; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.dsl.MessageChannelSpec; -import org.springframework.util.Assert; - -/** - * A {@link MessageChannelSpec} for a {@link AbstractAmqpChannel}s. - * - * @param the target {@link AmqpPollableMessageChannelSpec} implementation type. - * @param the target channel type. - * - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -public class AmqpPollableMessageChannelSpec, T extends AbstractAmqpChannel> - extends MessageChannelSpec { - - protected final AmqpChannelFactoryBean amqpChannelFactoryBean; // NOSONAR final - - protected AmqpPollableMessageChannelSpec(ConnectionFactory connectionFactory) { - this(new AmqpChannelFactoryBean(false), connectionFactory); - } - - AmqpPollableMessageChannelSpec(AmqpChannelFactoryBean amqpChannelFactoryBean, ConnectionFactory connectionFactory) { - this.amqpChannelFactoryBean = amqpChannelFactoryBean; - this.amqpChannelFactoryBean.setConnectionFactory(connectionFactory); - this.amqpChannelFactoryBean.setSingleton(false); - this.amqpChannelFactoryBean.setPubSub(false); - - /* - We need this artificial BeanFactory to overcome AmqpChannelFactoryBean initialization. - The real BeanFactory will be applied later for the target AbstractAmqpChannel instance. - */ - this.amqpChannelFactoryBean.setBeanFactory(new DefaultListableBeanFactory()); - } - - @Override - protected S id(String id) { - this.amqpChannelFactoryBean.setBeanName(id); - return super.id(id); - } - - /** - * Also implicitly sets the {@link #id(String)} (if not explicitly set). - * @param queueName the queueName. - * @return the spec. - * @see AmqpChannelFactoryBean#setQueueName(String) - */ - public S queueName(String queueName) { - if (getId() == null) { - id(queueName + ".channel"); - } - this.amqpChannelFactoryBean.setQueueName(queueName); - return _this(); - } - - /** - * @param encoding the encoding. - * @return the spec. - * @see org.springframework.amqp.rabbit.core.RabbitTemplate#setEncoding(String) - */ - public S encoding(String encoding) { - this.amqpChannelFactoryBean.setEncoding(encoding); - return _this(); - } - - /** - * @param messageConverter the messageConverter. - * @return the spec. - * @see org.springframework.amqp.rabbit.core.RabbitTemplate#setMessageConverter(MessageConverter) - */ - public S amqpMessageConverter(MessageConverter messageConverter) { - this.amqpChannelFactoryBean.setMessageConverter(messageConverter); - return _this(); - } - - /** - * Configure {@code channelTransacted} on both the - * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} (for sends) and - * {@link org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer} - * (for receives) when using Spring Integration 4.0. When using Spring Integration - * 4.1, only the container is configured. See {@link #templateChannelTransacted(boolean)}. - * @param channelTransacted the channelTransacted. - * @return the spec. - * @see org.springframework.amqp.rabbit.core.RabbitTemplate#setChannelTransacted(boolean) - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setChannelTransacted(boolean) - */ - public S channelTransacted(boolean channelTransacted) { - this.amqpChannelFactoryBean.setChannelTransacted(channelTransacted); - return _this(); - } - - /** - * Configure {@code channelTransacted} on the - * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} used when sending - * messages to the channel. Only applies when Spring Integration 4.1 or greater is - * being used. Otherwise, see {@link #channelTransacted(boolean)}. - * @param channelTransacted the channelTransacted. - * @return the spec. - * @see org.springframework.amqp.rabbit.core.RabbitTemplate#setChannelTransacted(boolean) - */ - public S templateChannelTransacted(boolean channelTransacted) { - this.amqpChannelFactoryBean.setTemplateChannelTransacted(channelTransacted); - return _this(); - } - - /** - * Configure {@code messagePropertiesConverter} on both the - * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} (for sends) and - * {@link org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer} - * (for receives). - * @param messagePropertiesConverter the messagePropertiesConverter. - * @return the spec. - * @see org.springframework.amqp.rabbit.core.RabbitTemplate#setMessagePropertiesConverter - * @see org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#setMessagePropertiesConverter - */ - public S messagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) { - this.amqpChannelFactoryBean.setMessagePropertiesConverter(messagePropertiesConverter); - return _this(); - } - - /** - * Configure the delivery mode for messages that don't have an - * {@link org.springframework.amqp.support.AmqpHeaders#DELIVERY_MODE} header. - * Default is {@link MessageDeliveryMode#PERSISTENT}. - * @param mode the mode. - * @return the spec. - */ - public S defaultDeliveryMode(MessageDeliveryMode mode) { - this.amqpChannelFactoryBean.setDefaultDeliveryMode(mode); - return _this(); - } - - /** - * Configure whether normal spring-messaging to AMQP message mapping is enabled. - * Default false. - * @param extract true to enable mapping. - * @return the spec. - * @see #outboundHeaderMapper(AmqpHeaderMapper) - * @see #inboundHeaderMapper(AmqpHeaderMapper) - */ - public S extractPayload(boolean extract) { - this.amqpChannelFactoryBean.setExtractPayload(extract); - return _this(); - } - - /** - * Configure the outbound header mapper to use when {@link #extractPayload(boolean)} - * is true. Defaults to a {@code DefaultAmqpHeaderMapper}. - * @param mapper the mapper. - * @return the spec. - * @see #extractPayload(boolean) - */ - public S outboundHeaderMapper(AmqpHeaderMapper mapper) { - this.amqpChannelFactoryBean.setOutboundHeaderMapper(mapper); - return _this(); - } - - /** - * Configure the inbound header mapper to use when {@link #extractPayload(boolean)} - * is true. Defaults to a {@code DefaultAmqpHeaderMapper}. - * @param mapper the mapper. - * @return the spec. - * @see #extractPayload(boolean) - */ - public S inboundHeaderMapper(AmqpHeaderMapper mapper) { - this.amqpChannelFactoryBean.setInboundHeaderMapper(mapper); - return _this(); - } - - /** - * @param headersLast true to map headers last. - * @return the spec. - * @see AbstractAmqpChannel#setHeadersMappedLast(boolean) - */ - public S headersMappedLast(boolean headersLast) { - this.target.setHeadersMappedLast(headersLast); - return _this(); - } - - @Override - @SuppressWarnings("unchecked") - protected T doGet() { - Assert.notNull(getId(), "The 'id' or 'queueName' must be specified"); - try { - this.channel = (T) this.amqpChannelFactoryBean.getObject(); - } - catch (Exception e) { - throw new BeanCreationException("Cannot create the AMQP MessageChannel", e); - } - return super.doGet(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpPublishSubscribeMessageChannelSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpPublishSubscribeMessageChannelSpec.java deleted file mode 100644 index 9a91a8e49bb..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpPublishSubscribeMessageChannelSpec.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.core.FanoutExchange; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.integration.amqp.channel.PollableAmqpChannel; - -/** - * A {@link AmqpMessageChannelSpec} for - * {@link org.springframework.integration.amqp.channel.PublishSubscribeAmqpChannel}s. - * - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -public class AmqpPublishSubscribeMessageChannelSpec - extends AmqpMessageChannelSpec { - - protected AmqpPublishSubscribeMessageChannelSpec(ConnectionFactory connectionFactory) { - super(connectionFactory); - this.amqpChannelFactoryBean.setPubSub(true); - } - - /** - * @param exchange the exchange. - * @return the spec. - * @see org.springframework.integration.amqp.config.AmqpChannelFactoryBean#setExchange(FanoutExchange) - */ - public AmqpPublishSubscribeMessageChannelSpec exchange(FanoutExchange exchange) { - this.amqpChannelFactoryBean.setExchange(exchange); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/DirectMessageListenerContainerSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/DirectMessageListenerContainerSpec.java deleted file mode 100644 index 1d233b7eafc..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/DirectMessageListenerContainerSpec.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; - -/** - * Spec for a {@link DirectMessageListenerContainer}. - * - * @author Gary Russell - * @since 5.0 - * - */ -public class DirectMessageListenerContainerSpec - extends AbstractMessageListenerContainerSpec { - - protected final DirectMessageListenerContainer listenerContainer; // NOSONAR - - public DirectMessageListenerContainerSpec(DirectMessageListenerContainer listenerContainer) { - super(listenerContainer); - this.listenerContainer = listenerContainer; - } - - /** - * @param consumersPerQueue the consumersPerQueue. - * @return the spec. - * @see DirectMessageListenerContainer#setConsumersPerQueue(int) - */ - public DirectMessageListenerContainerSpec consumersPerQueue(int consumersPerQueue) { - this.listenerContainer.setConsumersPerQueue(consumersPerQueue); - return this; - } - - /** - * @param messagesPerAck the messages per ack. - * @return the spec. - * @see DirectMessageListenerContainer#setMessagesPerAck(int) - */ - public DirectMessageListenerContainerSpec messagesPerAck(int messagesPerAck) { - this.listenerContainer.setMessagesPerAck(messagesPerAck); - return this; - } - - /** - * @param ackTimeout the ack timeout. - * @return the spec. - * @see DirectMessageListenerContainer#setAckTimeout(long) - */ - public DirectMessageListenerContainerSpec ackTimeout(long ackTimeout) { - this.listenerContainer.setAckTimeout(ackTimeout); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/MessageListenerContainerSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/MessageListenerContainerSpec.java deleted file mode 100644 index 2630d46b4d0..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/MessageListenerContainerSpec.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.listener.MessageListenerContainer; -import org.springframework.integration.dsl.IntegrationComponentSpec; - -/** - * Base class for container specs. - * - * @param the current spec extension type - * @param the listener container type - * - * @author Gary Russell - * - * @since 6.0 - * - */ -public abstract class MessageListenerContainerSpec, - C extends MessageListenerContainer> - extends IntegrationComponentSpec { - - /** - * Set the queue names. - * @param queueNames the queue names. - * @return this spec. - */ - public S queueName(String... queueNames) { - this.target.setQueueNames(queueNames); - return _this(); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStream.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStream.java deleted file mode 100644 index 8007ef13793..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStream.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import com.rabbitmq.stream.Codec; -import com.rabbitmq.stream.Environment; -import org.jspecify.annotations.Nullable; - -import org.springframework.rabbit.stream.listener.StreamListenerContainer; -import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; - -/** - * Factory class for RabbitMQ components. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 6.0 - * - */ -public final class RabbitStream { - - private RabbitStream() { - } - - /** - * Create an initial {@link RabbitStreamInboundChannelAdapterSpec} - * with the provided {@link StreamListenerContainer}. - * The {@code streamName} or {@code superStream} must be provided after creation of this spec; - * or the {@code listenerContainer} options should be specified - * on the provided {@link StreamListenerContainer} using - * {@link RabbitStreamInboundChannelAdapterSpec#configureContainer(java.util.function.Consumer)}. - * @param listenerContainer the listenerContainer. - * @return the RabbitInboundChannelAdapterSLCSpec. - */ - public static RabbitStreamInboundChannelAdapterSpec inboundAdapter(StreamListenerContainer listenerContainer) { - return new RabbitStreamInboundChannelAdapterSpec(listenerContainer); - } - - /** - * Create an initial {@link RabbitStreamInboundChannelAdapterSpec} - * with the provided {@link Environment}. - * The {@code streamName} or {@code superStream} must be provided after creation of this spec; - * or the {@code listenerContainer} options should be specified - * on the provided {@link StreamListenerContainer} using - * {@link RabbitStreamInboundChannelAdapterSpec#configureContainer(java.util.function.Consumer)}. - * @param environment the environment. - * @return the RabbitInboundChannelAdapterSLCSpec. - */ - public static RabbitStreamInboundChannelAdapterSpec inboundAdapter(Environment environment) { - return inboundAdapter(environment, null); - } - - /** - * Create an initial {@link RabbitStreamInboundChannelAdapterSpec} - * with the provided {@link Environment}. - * The {@code streamName} or {@code superStream} must be provided after creation of this spec; - * or the {@code listenerContainer} options should be specified - * on the provided {@link StreamListenerContainer} using - * {@link RabbitStreamInboundChannelAdapterSpec#configureContainer(java.util.function.Consumer)}. - * @param environment the environment. - * @param codec the codec. - * @return the RabbitInboundChannelAdapterSLCSpec. - */ - public static RabbitStreamInboundChannelAdapterSpec inboundAdapter(Environment environment, @Nullable Codec codec) { - return new RabbitStreamInboundChannelAdapterSpec(environment, codec); - } - - /** - * Create an initial {@link RabbitStreamMessageHandlerSpec} (adapter). - * @param environment the environment. - * @param streamName the name of stream to produce. - * @return the RabbitStreamMessageHandlerSpec. - * @since 6.1 - */ - public static RabbitStreamMessageHandlerSpec outboundStreamAdapter(Environment environment, String streamName) { - return outboundStreamAdapter(new RabbitStreamTemplate(environment, streamName)); - } - - /** - * Create an initial {@link RabbitStreamMessageHandlerSpec} (adapter). - * @param template the amqpTemplate. - * @return the RabbitStreamMessageHandlerSpec. - */ - public static RabbitStreamMessageHandlerSpec outboundStreamAdapter(RabbitStreamTemplate template) { - return new RabbitStreamMessageHandlerSpec(template); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamInboundChannelAdapterSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamInboundChannelAdapterSpec.java deleted file mode 100644 index 8daa2d96f7e..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamInboundChannelAdapterSpec.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Consumer; - -import com.rabbitmq.stream.Codec; -import com.rabbitmq.stream.Environment; -import org.jspecify.annotations.Nullable; - -import org.springframework.rabbit.stream.listener.StreamListenerContainer; - -/** - * Spec for an inbound channel adapter with a {@link StreamListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 6.0 - * - */ -public class RabbitStreamInboundChannelAdapterSpec - extends AmqpInboundChannelAdapterSpec { - - protected RabbitStreamInboundChannelAdapterSpec(StreamListenerContainer listenerContainer) { - super(new RabbitStreamMessageListenerContainerSpec(listenerContainer)); - } - - protected RabbitStreamInboundChannelAdapterSpec(Environment environment, @Nullable Codec codec) { - super(new RabbitStreamMessageListenerContainerSpec(environment, codec)); - } - - /** - * Configure a name for Rabbit stream to consume from. - * @param streamName the name of Rabbit stream. - * @return the spec - * @since 6.1 - */ - public RabbitStreamInboundChannelAdapterSpec streamName(String streamName) { - this.listenerContainerSpec.queueName(streamName); - return this; - } - - /** - * Configure a name for Rabbit super stream to consume from. - * @param superStream the name of Rabbit super stream. - * @param consumerName the logical name to enable offset tracking. - * @return the spec - * @since 6.1 - */ - public RabbitStreamInboundChannelAdapterSpec superStream(String superStream, String consumerName) { - return superStream(superStream, consumerName, 1); - } - - /** - * Configure a name for Rabbit super stream to consume from. - * @param superStream the name of Rabbit super stream. - * @param consumerName the logical name to enable offset tracking. - * @param consumers the number of consumers. - * @return the spec - * @since 6.1 - */ - public RabbitStreamInboundChannelAdapterSpec superStream(String superStream, String consumerName, int consumers) { - ((RabbitStreamMessageListenerContainerSpec) this.listenerContainerSpec) - .superStream(superStream, consumerName, consumers); - return this; - } - - public RabbitStreamInboundChannelAdapterSpec configureContainer( - Consumer configurer) { - - configurer.accept((RabbitStreamMessageListenerContainerSpec) this.listenerContainerSpec); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamMessageHandlerSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamMessageHandlerSpec.java deleted file mode 100644 index b099ec2e27d..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamMessageHandlerSpec.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.integration.amqp.outbound.RabbitStreamMessageHandler; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.dsl.MessageHandlerSpec; -import org.springframework.messaging.MessageChannel; -import org.springframework.rabbit.stream.producer.RabbitStreamOperations; - -/** - * The base {@link MessageHandlerSpec} for {@link RabbitStreamMessageHandler}s. - * - * @author Gary Russell - * - * @since 6.0 - */ -public class RabbitStreamMessageHandlerSpec - extends MessageHandlerSpec { - - private final DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - - RabbitStreamMessageHandlerSpec(RabbitStreamOperations operations) { - this.target = new RabbitStreamMessageHandler(operations); - } - - /** - * Set a custom {@link AmqpHeaderMapper} for mapping request and reply headers. - * @param headerMapper the {@link AmqpHeaderMapper} to use. - * @return this spec. - */ - public RabbitStreamMessageHandlerSpec headerMapper(AmqpHeaderMapper headerMapper) { - this.target.setHeaderMapper(headerMapper); - return this; - } - - /** - * Provide the header names that should be mapped from a request to a - * {@link org.springframework.messaging.MessageHeaders}. - * @param headers The request header names. - * @return this spec. - */ - public RabbitStreamMessageHandlerSpec mappedRequestHeaders(String... headers) { - this.headerMapper.setRequestHeaderNames(headers); - return this; - } - - /** - * Determine whether the headers are - * mapped before the message is converted, or afterwards. - * @param headersLast true to map headers last. - * @return this spec. - * @see RabbitStreamMessageHandler#setHeadersMappedLast(boolean) - */ - public RabbitStreamMessageHandlerSpec headersMappedLast(boolean headersLast) { - this.target.setHeadersMappedLast(headersLast); - return this; - } - - /** - * Set the success channel. - * @param channel the channel. - * @return this spec. - */ - public RabbitStreamMessageHandlerSpec sendSuccessChannel(MessageChannel channel) { - this.target.setSendSuccessChannel(channel); - return this; - } - - /** - * Set the failure channel. After a send failure, an - * {@link org.springframework.messaging.support.ErrorMessage} will be sent - * to this channel with a payload of the exception with the - * failed message. - * @param channel the channel. - * @return this spec. - */ - public RabbitStreamMessageHandlerSpec sendFailureChannel(MessageChannel channel) { - this.target.setSendFailureChannel(channel); - return this; - } - - /** - * Set the success channel. - * @param channel the channel. - * @return this spec. - */ - public RabbitStreamMessageHandlerSpec sendSuccessChannel(String channel) { - this.target.setSendSuccessChannelName(channel); - return this; - } - - /** - * Set the failure channel. After a send failure, an - * {@link org.springframework.messaging.support.ErrorMessage} will be sent - * to this channel with a payload of the exception with the - * failed message. - * @param channel the channel. - * @return this spec. - */ - public RabbitStreamMessageHandlerSpec sendFailureChannel(String channel) { - this.target.setSendFailureChannelName(channel); - return this; - } - - /** - * Set to true to wait for a confirmation. - * @param sync true to wait. - * @return this spec. - * @see #confirmTimeout(long) - */ - public RabbitStreamMessageHandlerSpec sync(boolean sync) { - this.target.setSync(sync); - return this; - } - - /** - * Set a timeout for the confirm result. - * @param timeout the approximate timeout. - * @return this spec. - * @see #sync(boolean) - */ - public RabbitStreamMessageHandlerSpec confirmTimeout(long timeout) { - this.target.setConfirmTimeout(timeout); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamMessageListenerContainerSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamMessageListenerContainerSpec.java deleted file mode 100644 index 97dca6d506e..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/RabbitStreamMessageListenerContainerSpec.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.util.function.Consumer; - -import com.rabbitmq.stream.Codec; -import com.rabbitmq.stream.Environment; -import org.aopalliance.aop.Advice; -import org.jspecify.annotations.Nullable; - -import org.springframework.rabbit.stream.listener.ConsumerCustomizer; -import org.springframework.rabbit.stream.listener.StreamListenerContainer; -import org.springframework.rabbit.stream.support.converter.StreamMessageConverter; - -/** - * Spec for {@link StreamListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 6.0 - * - */ -public class RabbitStreamMessageListenerContainerSpec extends - MessageListenerContainerSpec { - - RabbitStreamMessageListenerContainerSpec(StreamListenerContainer container) { - this.target = container; - } - - RabbitStreamMessageListenerContainerSpec(Environment environment, @Nullable Codec codec) { - this.target = new StreamListenerContainer(environment, codec); - } - - /** - * Enable Single Active Consumer on a Super Stream. - * Mutually exclusive with {@link #queueName(String...)}. - * @param superStream the stream. - * @param name the consumer name. - * @return this spec. - */ - public RabbitStreamMessageListenerContainerSpec superStream(String superStream, String name) { - return superStream(superStream, name, 1); - } - - /** - * Enable Single Active Consumer on a Super Stream. - * Mutually exclusive with {@link #queueName(String...)}. - * @param superStream the stream. - * @param name the consumer name. - * @param consumers the number of consumers. - * @return this spec. - * @since 6.1 - */ - public RabbitStreamMessageListenerContainerSpec superStream(String superStream, String name, int consumers) { - this.target.superStream(superStream, name, consumers); - return this; - } - - /** - * Set a stream message converter. - * @param converter the converter. - * @return this spec. - */ - public RabbitStreamMessageListenerContainerSpec streamConverter(StreamMessageConverter converter) { - this.target.setStreamConverter(converter); - return this; - } - - /** - * Set a consumer customizer. - * @param customizer the customizer. - * @return this spec. - */ - public RabbitStreamMessageListenerContainerSpec consumerCustomizer(ConsumerCustomizer customizer) { - this.target.setConsumerCustomizer(customizer); - return this; - } - - /** - * @param adviceChain the adviceChain. - * @return the spec. - * @see StreamListenerContainer#setAdviceChain(Advice[]) - */ - public RabbitStreamMessageListenerContainerSpec adviceChain(Advice... adviceChain) { - this.target.setAdviceChain(adviceChain); - return this; - } - - /** - * Perform additional configuration of the container. - * @param consumer a consumer for the container. - * @return this spec. - */ - public RabbitStreamMessageListenerContainerSpec configure(Consumer consumer) { - consumer.accept(this.target); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/SimpleMessageListenerContainerSpec.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/SimpleMessageListenerContainerSpec.java deleted file mode 100644 index e4d329fad6c..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/SimpleMessageListenerContainerSpec.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; - -/** - * Spec for a {@link SimpleMessageListenerContainer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public class SimpleMessageListenerContainerSpec extends - AbstractMessageListenerContainerSpec { - - protected final SimpleMessageListenerContainer listenerContainer; // NOSONAR - - public SimpleMessageListenerContainerSpec(SimpleMessageListenerContainer listenerContainer) { - super(listenerContainer); - this.listenerContainer = listenerContainer; - } - - /** - * @param concurrentConsumers the concurrentConsumers - * @return the spec. - * @see SimpleMessageListenerContainer#setConcurrentConsumers(int) - */ - public SimpleMessageListenerContainerSpec concurrentConsumers(int concurrentConsumers) { - this.listenerContainer.setConcurrentConsumers(concurrentConsumers); - return this; - } - - /** - * @param maxConcurrentConsumers the maxConcurrentConsumers. - * @return the spec. - * @see SimpleMessageListenerContainer#setMaxConcurrentConsumers(int) - */ - public SimpleMessageListenerContainerSpec maxConcurrentConsumers(int maxConcurrentConsumers) { - this.listenerContainer.setMaxConcurrentConsumers(maxConcurrentConsumers); - return this; - } - - /** - * @param startConsumerMinInterval the startConsumerMinInterval - * @return the spec. - * @see SimpleMessageListenerContainer#setStartConsumerMinInterval(long) - */ - public SimpleMessageListenerContainerSpec startConsumerMinInterval(long startConsumerMinInterval) { - this.listenerContainer.setStartConsumerMinInterval(startConsumerMinInterval); - return this; - } - - /** - * @param stopConsumerMinInterval the stopConsumerMinInterval. - * @return the spec. - * @see SimpleMessageListenerContainer#setStopConsumerMinInterval(long) - */ - public SimpleMessageListenerContainerSpec stopConsumerMinInterval(long stopConsumerMinInterval) { - this.listenerContainer.setStopConsumerMinInterval(stopConsumerMinInterval); - return this; - } - - /** - * @param consecutiveActiveTrigger the consecutiveActiveTrigger. - * @return the spec. - * @see SimpleMessageListenerContainer#setConsecutiveActiveTrigger(int) - */ - public SimpleMessageListenerContainerSpec consecutiveActiveTrigger(int consecutiveActiveTrigger) { - this.listenerContainer.setConsecutiveActiveTrigger(consecutiveActiveTrigger); - return this; - } - - /** - * @param consecutiveIdleTrigger the consecutiveIdleTrigger. - * @return the spec. - * @see SimpleMessageListenerContainer#setConsecutiveIdleTrigger(int) - */ - public SimpleMessageListenerContainerSpec consecutiveIdleTrigger(int consecutiveIdleTrigger) { - this.listenerContainer.setConsecutiveIdleTrigger(consecutiveIdleTrigger); - return this; - } - - /** - * @param receiveTimeout the receiveTimeout - * @return the spec. - * @see SimpleMessageListenerContainer#setReceiveTimeout(long) - */ - public SimpleMessageListenerContainerSpec receiveTimeout(long receiveTimeout) { - this.listenerContainer.setReceiveTimeout(receiveTimeout); - return this; - } - - /** - * The batch size to use. - * @param batchSize the batchSize. - * @return the spec. - * @since 5.2 - * @see SimpleMessageListenerContainer#setBatchSize(int) - */ - public SimpleMessageListenerContainerSpec batchSize(int batchSize) { - this.listenerContainer.setBatchSize(batchSize); - return this; - } - - /** - * Set to true to enable batching of consumed messages. - * @param enabled true to enable. - * @return the spec. - * @since 5.3 - */ - public SimpleMessageListenerContainerSpec consumerBatchEnabled(boolean enabled) { - this.listenerContainer.setConsumerBatchEnabled(enabled); - return this; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/package-info.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/package-info.java deleted file mode 100644 index 51511d4de21..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides AMQP Component support for the Java DSL. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.amqp.dsl; diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundChannelAdapter.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundChannelAdapter.java deleted file mode 100644 index ecc4a0cf453..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundChannelAdapter.java +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import com.rabbitmq.client.Channel; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.batch.BatchingStrategy; -import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.MessageListenerContainer; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareBatchMessageListener; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; -import org.springframework.amqp.rabbit.retry.MessageBatchRecoverer; -import org.springframework.amqp.rabbit.retry.MessageRecoverer; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.MessageConversionException; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.core.AttributeAccessor; -import org.springframework.core.retry.RetryException; -import org.springframework.core.retry.RetryOperations; -import org.springframework.core.retry.RetryTemplate; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.AmqpMessageHeaderErrorMessageStrategy; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.EndpointUtils; -import org.springframework.integration.context.OrderlyShutdownCapable; -import org.springframework.integration.core.RecoveryCallback; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.support.ErrorMessageUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.util.Assert; - -/** - * Adapter that receives Messages from an AMQP Queue, converts them into - * Spring Integration messages and sends the results to a Message Channel. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 2.1 - */ -public class AmqpInboundChannelAdapter extends MessageProducerSupport implements - OrderlyShutdownCapable { - - /** - * Header containing {@code List} headers when batch mode - * is {@link BatchMode#EXTRACT_PAYLOADS_WITH_HEADERS}. - */ - public static final String CONSOLIDATED_HEADERS = AmqpHeaders.PREFIX + "batchedHeaders"; - - /** - * Defines the payload type when the listener container is configured with consumerBatchEnabled. - */ - public enum BatchMode { - - /** - * Payload is a {@code List>} where each element is a message is - * converted from the Spring AMQP Message. - */ - MESSAGES, - - /** - * Payload is a {@code List} where each element is the converted body of the - * Spring AMQP Message. - */ - EXTRACT_PAYLOADS, - - /** - * Payload is a {@code List} where each element is the converted body of the - * Spring AMQP Message. The headers for each message are provided in a header - * {@link AmqpInboundChannelAdapter#CONSOLIDATED_HEADERS}. - */ - EXTRACT_PAYLOADS_WITH_HEADERS - - } - - private static final ThreadLocal<@Nullable AttributeAccessor> ATTRIBUTES_HOLDER = new ThreadLocal<>(); - - private final MessageListenerContainer messageListenerContainer; - - private final @Nullable AbstractMessageListenerContainer abstractListenerContainer; - - private MessageConverter messageConverter = new SimpleMessageConverter(); - - private AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - - private @Nullable RetryTemplate retryTemplate; - - private @Nullable RecoveryCallback recoveryCallback; - - private @Nullable MessageRecoverer messageRecoverer; - - private BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(0, 0, 0L); - - private boolean bindSourceMessage; - - private BatchMode batchMode = BatchMode.MESSAGES; - - private String headerNameForBatchedHeaders = CONSOLIDATED_HEADERS; - - /** - * Construct an instance using the provided container. - * @param listenerContainer the container. - */ - @SuppressWarnings("this-escape") - public AmqpInboundChannelAdapter(MessageListenerContainer listenerContainer) { - Assert.notNull(listenerContainer, "listenerContainer must not be null"); - Assert.isNull(listenerContainer.getMessageListener(), - "The listenerContainer provided to an AMQP inbound Channel Adapter " + - "must not have a MessageListener configured since the adapter " + - "configure its own listener implementation."); - this.messageListenerContainer = listenerContainer; - this.messageListenerContainer.setAutoStartup(false); - setErrorMessageStrategy(new AmqpMessageHeaderErrorMessageStrategy()); - this.abstractListenerContainer = - listenerContainer instanceof AbstractMessageListenerContainer abstractMessageListenerContainer - ? abstractMessageListenerContainer - : null; - } - - public void setMessageConverter(MessageConverter messageConverter) { - Assert.notNull(messageConverter, "messageConverter must not be null"); - this.messageConverter = messageConverter; - } - - public void setHeaderMapper(AmqpHeaderMapper headerMapper) { - Assert.notNull(headerMapper, "headerMapper must not be null"); - this.headerMapper = headerMapper; - } - - /** - * Set a {@link RetryTemplate} to use for retrying a message delivery within the - * adapter. Unlike adding retry at the container level, this can be used with an - * {@code ErrorMessageSendingRecoverer} {@link RecoveryCallback} to publish to the - * error channel after retries are exhausted. You generally should not configure an - * error channel when using retry here, use a {@link RecoveryCallback} instead. - * @param retryTemplate the template. - * @since 4.3.10. - * @see #setRecoveryCallback(RecoveryCallback) - */ - public void setRetryTemplate(RetryTemplate retryTemplate) { - this.retryTemplate = retryTemplate; - } - - /** - * Set a {@link RecoveryCallback} when using retry within the adapter. - * Mutually exclusive with {@link #setMessageRecoverer(MessageRecoverer)}. - * @param recoveryCallback the callback. - * @since 4.3.10 - * @see #setRetryTemplate(RetryTemplate) - */ - public void setRecoveryCallback(RecoveryCallback recoveryCallback) { - this.recoveryCallback = recoveryCallback; - } - - /** - * Configure a {@link MessageRecoverer} for retry operations. - * A more AMQP-specific convenience instead of {@link #setRecoveryCallback(RecoveryCallback)}. - * @param messageRecoverer the {@link MessageRecoverer} to use. - * @since 5.5 - */ - public void setMessageRecoverer(MessageRecoverer messageRecoverer) { - this.messageRecoverer = messageRecoverer; - } - - /** - * Set a batching strategy to use when de-batching messages created by a batching - * producer (such as the BatchingRabbitTemplate). - * Default is {@link SimpleBatchingStrategy}. - * @param batchingStrategy the strategy. - * @since 5.2 - */ - public void setBatchingStrategy(BatchingStrategy batchingStrategy) { - Assert.notNull(batchingStrategy, "'batchingStrategy' cannot be null"); - this.batchingStrategy = batchingStrategy; - } - - /** - * Set to true to bind the source message in the header named - * {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}. - * @param bindSourceMessage true to bind. - * @since 5.1.6 - */ - public void setBindSourceMessage(boolean bindSourceMessage) { - this.bindSourceMessage = bindSourceMessage; - } - - /** - * When the listener container is configured with consumerBatchEnabled, set the payload - * type for messages generated for the batches. Default is {@link BatchMode#MESSAGES}. - * @param batchMode the batch mode. - * @since 5.3 - */ - public void setBatchMode(BatchMode batchMode) { - Assert.notNull(batchMode, "'batchMode' cannot be null"); - this.batchMode = batchMode; - } - - /** - * Set a header name containing {@code List} headers when batch mode - * is {@link BatchMode#EXTRACT_PAYLOADS_WITH_HEADERS}. - * Defaults to {@link #CONSOLIDATED_HEADERS}. - * @param headerNameForBatchedHeaders the name of header - * containing {@code List} headers when batch mode - * is {@link BatchMode#EXTRACT_PAYLOADS_WITH_HEADERS}. - * @since 6.4 - */ - public void setHeaderNameForBatchedHeaders(String headerNameForBatchedHeaders) { - Assert.hasText(headerNameForBatchedHeaders, "'headerNameForBatchedHeaders' must not be empty"); - this.headerNameForBatchedHeaders = headerNameForBatchedHeaders; - } - - @Override - public String getComponentType() { - return "amqp:inbound-channel-adapter"; - } - - @Override - protected void onInit() { - if (this.retryTemplate != null) { - Assert.state(getErrorChannel() == null, "Cannot have an 'errorChannel' property when a 'RetryTemplate' is " - + "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to " - + "send an error message when retries are exhausted"); - setupRecoveryCallbackIfAny(); - } - Listener messageListener; - if (this.messageListenerContainer.isConsumerBatchEnabled()) { - messageListener = new BatchListener(); - } - else { - messageListener = new Listener(); - } - this.messageListenerContainer.setupMessageListener(messageListener); - this.messageListenerContainer.afterPropertiesSet(); - super.onInit(); - } - - private void setupRecoveryCallbackIfAny() { - MessageRecoverer messageRecovererToUse = this.messageRecoverer; - Assert.state(this.recoveryCallback == null || messageRecovererToUse == null, - "Only one of 'recoveryCallback' or 'messageRecoverer' may be provided, but not both"); - if (messageRecovererToUse != null) { - if (this.messageListenerContainer.isConsumerBatchEnabled()) { - Assert.isInstanceOf(MessageBatchRecoverer.class, messageRecovererToUse, - "The 'messageRecoverer' must be an instance of MessageBatchRecoverer " + - "when consumer configured for batch mode"); - this.recoveryCallback = - (context, cause) -> { - @SuppressWarnings("unchecked") - List messagesToRecover = - (List) context.getAttribute(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE); - if (messagesToRecover != null) { - ((MessageBatchRecoverer) messageRecovererToUse) - .recover(messagesToRecover, cause); - } - return null; - }; - } - else { - this.recoveryCallback = - (context, cause) -> { - Message messageToRecover = - (Message) context.getAttribute(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE); - if (messageToRecover != null) { - messageRecovererToUse.recover(messageToRecover, cause); - } - return null; - }; - } - } - } - - @Override - protected void doStart() { - this.messageListenerContainer.start(); - } - - @Override - protected void doStop() { - this.messageListenerContainer.stop(); - } - - @Override - public int beforeShutdown() { - this.stop(); - return 0; - } - - @Override - public int afterShutdown() { - return 0; - } - - /** - * If there's a retry template, it will set the attribute holder via the listener. If - * there's no retry template, but there's an error channel, we create a new attribute - * holder here. If an attribute holder exists (by either method), we set the - * attributes for use by the - * {@link org.springframework.integration.support.ErrorMessageStrategy}. - * @param amqpMessage the AMQP message to use. - * @param message the Spring Messaging message to use. - * @since 4.3.10 - */ - private void setAttributesIfNecessary(Object amqpMessage, - org.springframework.messaging.@Nullable Message message) { - - boolean needHolder = getErrorChannel() != null || this.retryTemplate != null; - if (needHolder) { - AttributeAccessor attributes = ATTRIBUTES_HOLDER.get(); - if (attributes == null) { - attributes = ErrorMessageUtils.getAttributeAccessor(null, null); - ATTRIBUTES_HOLDER.set(attributes); - } - attributes.setAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY, message); - attributes.setAttribute(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, amqpMessage); - } - } - - @Override - protected AttributeAccessor getErrorMessageAttributes(org.springframework.messaging.@Nullable Message message) { - AttributeAccessor attributes = ATTRIBUTES_HOLDER.get(); - if (attributes == null) { - return super.getErrorMessageAttributes(message); - } - else { - return attributes; - } - } - - protected class Listener implements ChannelAwareMessageListener { - - protected final MessageConverter converter = AmqpInboundChannelAdapter.this.messageConverter; // NOSONAR - - protected final boolean manualAcks = - AmqpInboundChannelAdapter.this.abstractListenerContainer != null - && AcknowledgeMode.MANUAL == - AmqpInboundChannelAdapter.this.abstractListenerContainer.getAcknowledgeMode(); - - protected final @Nullable RetryOperations retryOps = AmqpInboundChannelAdapter.this.retryTemplate; - - protected final @Nullable RecoveryCallback recoverer = AmqpInboundChannelAdapter.this.recoveryCallback; - - protected Listener() { - } - - @Override - public void onMessage(final Message message, @Nullable Channel channel) { - try { - if (this.retryOps == null) { - createAndSend(message, channel); - } - else { - final org.springframework.messaging.Message toSend = - createMessageFromAmqp(message, channel); - try { - this.retryOps.execute( - () -> { - AtomicInteger deliveryAttempt = StaticMessageHeaderAccessor.getDeliveryAttempt(toSend); - if (deliveryAttempt != null) { - deliveryAttempt.incrementAndGet(); - } - setAttributesIfNecessary(message, toSend); - sendMessage(toSend); - return null; - }); - } - catch (RetryException ex) { - if (this.recoverer != null) { - this.recoverer.recover(getErrorMessageAttributes(toSend), ex.getCause()); - } - else { - throw new ListenerExecutionFailedException( - "Failed handling message after '" + ex.getRetryCount() + "' retries", ex, message); - } - } - } - } - catch (MessageConversionException e) { - MessageChannel errorChannel = getErrorChannel(); - if (errorChannel != null) { - setAttributesIfNecessary(message, null); - getMessagingTemplate() - .send(errorChannel, - buildErrorMessage(null, - EndpointUtils.errorMessagePayload(message, channel, this.manualAcks, e))); - } - else { - throw e; - } - } - finally { - ATTRIBUTES_HOLDER.remove(); - } - } - - private void createAndSend(Message message, @Nullable Channel channel) { - org.springframework.messaging.Message messagingMessage = createMessageFromAmqp(message, channel); - setAttributesIfNecessary(message, messagingMessage); - sendMessage(messagingMessage); - } - - protected org.springframework.messaging.Message createMessageFromAmqp(Message message, - @Nullable Channel channel) { - - Object payload = convertPayload(message); - Map headers = - AmqpInboundChannelAdapter.this.headerMapper.toHeadersFromRequest(message.getMessageProperties()); - if (AmqpInboundChannelAdapter.this.bindSourceMessage) { - headers.put(IntegrationMessageHeaderAccessor.SOURCE_DATA, message); - } - long deliveryTag = message.getMessageProperties().getDeliveryTag(); - return createMessageFromPayload(payload, channel, headers, deliveryTag, null); - } - - protected Object convertPayload(Message message) { - Object payload; - if (AmqpInboundChannelAdapter.this.batchingStrategy.canDebatch(message.getMessageProperties())) { - List payloads = new ArrayList<>(); - AmqpInboundChannelAdapter.this.batchingStrategy.deBatch(message, - fragment -> payloads.add(this.converter.fromMessage(fragment))); - payload = payloads; - } - else { - payload = this.converter.fromMessage(message); - } - return payload; - } - - protected org.springframework.messaging.Message createMessageFromPayload(Object payload, - @Nullable Channel channel, Map headers, long deliveryTag, - @Nullable List> listHeaders) { - - if (this.manualAcks) { - headers.put(AmqpHeaders.DELIVERY_TAG, deliveryTag); - headers.put(AmqpHeaders.CHANNEL, channel); - } - if (AmqpInboundChannelAdapter.this.retryTemplate != null) { - headers.put(IntegrationMessageHeaderAccessor.DELIVERY_ATTEMPT, new AtomicInteger()); - } - if (listHeaders != null) { - headers.put(AmqpInboundChannelAdapter.this.headerNameForBatchedHeaders, listHeaders); - } - return getMessageBuilderFactory() - .withPayload(payload) - .copyHeaders(headers) - .build(); - } - - } - - protected class BatchListener extends Listener implements ChannelAwareBatchMessageListener { - - private final boolean batchModeMessages = BatchMode.MESSAGES.equals(AmqpInboundChannelAdapter.this.batchMode); - - @Override - public void onMessageBatch(List messages, @Nullable Channel channel) { - List converted; - List> headers = null; - if (this.batchModeMessages) { - converted = convertMessages(messages, channel); - } - else { - converted = convertPayloads(messages, channel); - if (BatchMode.EXTRACT_PAYLOADS_WITH_HEADERS.equals(AmqpInboundChannelAdapter.this.batchMode)) { - List> listHeaders = new ArrayList<>(); - messages.forEach(msg -> listHeaders.add(AmqpInboundChannelAdapter.this.headerMapper - .toHeadersFromRequest(msg.getMessageProperties()))); - headers = listHeaders; - } - } - if (converted != null) { - org.springframework.messaging.Message message = - createMessageFromPayload(converted, channel, new HashMap<>(), - messages.get(messages.size() - 1).getMessageProperties().getDeliveryTag(), headers); - try { - if (this.retryOps == null) { - setAttributesIfNecessary(messages, message); - sendMessage(message); - } - else { - try { - this.retryOps.execute( - () -> { - AtomicInteger deliveryAttempt = - StaticMessageHeaderAccessor.getDeliveryAttempt(message); - if (deliveryAttempt != null) { - deliveryAttempt.incrementAndGet(); - } - if (this.batchModeMessages) { - @SuppressWarnings("unchecked") - List> payloads = - (List>) message.getPayload(); - payloads.forEach(payload -> { - AtomicInteger batchItemDeliveryAttempt = - StaticMessageHeaderAccessor.getDeliveryAttempt(payload); - if (batchItemDeliveryAttempt != null) { - batchItemDeliveryAttempt.incrementAndGet(); - } - }); - } - setAttributesIfNecessary(messages, message); - sendMessage(message); - return null; - }); - } - catch (RetryException ex) { - if (this.recoverer != null) { - this.recoverer.recover(getErrorMessageAttributes(null), ex.getCause()); - } - else { - throw new ListenerExecutionFailedException( - "Failed handling messages after '" + ex.getRetryCount() + "' retries", ex, - messages.toArray(new Message[0])); - } - } - } - } - finally { - ATTRIBUTES_HOLDER.remove(); - } - } - } - - private @Nullable List> convertMessages(List messages, - @Nullable Channel channel) { - - List> converted = new ArrayList<>(); - try { - messages.forEach(message -> converted.add(createMessageFromAmqp(message, channel))); - return converted; - } - catch (MessageConversionException e) { - MessageChannel errorChannel = getErrorChannel(); - if (errorChannel != null) { - setAttributesIfNecessary(messages, null); - getMessagingTemplate() - .send(errorChannel, buildErrorMessage(null, - EndpointUtils.errorMessagePayload(messages, channel, this.manualAcks, e))); - } - else { - throw e; - } - } - return null; - } - - private @Nullable List convertPayloads(List messages, @Nullable Channel channel) { - List converted = new ArrayList<>(); - try { - messages.forEach(message -> converted.add(this.converter.fromMessage(message))); - return converted; - } - catch (MessageConversionException e) { - MessageChannel errorChannel = getErrorChannel(); - if (errorChannel != null) { - setAttributesIfNecessary(messages, null); - getMessagingTemplate() - .send(errorChannel, buildErrorMessage(null, - EndpointUtils.errorMessagePayload(messages, channel, this.manualAcks, e))); - } - else { - throw e; - } - } - return null; - } - - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundGateway.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundGateway.java deleted file mode 100644 index 094ea03dfb6..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundGateway.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import com.rabbitmq.client.Channel; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.Address; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.batch.BatchingStrategy; -import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.MessageListenerContainer; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; -import org.springframework.amqp.rabbit.retry.MessageRecoverer; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.core.AttributeAccessor; -import org.springframework.core.retry.RetryException; -import org.springframework.core.retry.RetryTemplate; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.AmqpMessageHeaderErrorMessageStrategy; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.EndpointUtils; -import org.springframework.integration.amqp.support.MappingUtils; -import org.springframework.integration.core.RecoveryCallback; -import org.springframework.integration.gateway.MessagingGatewaySupport; -import org.springframework.integration.support.ErrorMessageUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.util.Assert; - -/** - * Adapter that receives Messages from an AMQP Queue, converts them into - * Spring Integration messages and sends the results to a Message Channel. - * If a reply Message is received, it will be converted and sent back to - * the AMQP 'replyTo'. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * - * @since 2.1 - */ -public class AmqpInboundGateway extends MessagingGatewaySupport { - - private static final ThreadLocal<@Nullable AttributeAccessor> ATTRIBUTES_HOLDER = new ThreadLocal<>(); - - private final MessageListenerContainer messageListenerContainer; - - private final @Nullable AbstractMessageListenerContainer abstractListenerContainer; - - private final AmqpTemplate amqpTemplate; - - private final boolean amqpTemplateExplicitlySet; - - private MessageConverter amqpMessageConverter = new SimpleMessageConverter(); - - private MessageConverter templateMessageConverter = this.amqpMessageConverter; - - private AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - - private @Nullable Address defaultReplyTo; - - private @Nullable RetryTemplate retryTemplate; - - private @Nullable RecoveryCallback recoveryCallback; - - private @Nullable MessageRecoverer messageRecoverer; - - private BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(0, 0, 0L); - - private boolean bindSourceMessage; - - private boolean replyHeadersMappedLast; - - @SuppressWarnings("this-escape") - public AmqpInboundGateway(AbstractMessageListenerContainer listenerContainer) { - this(listenerContainer, new RabbitTemplate(listenerContainer.getConnectionFactory()), false); - } - - /** - * Construct {@link AmqpInboundGateway} based on the provided {@link MessageListenerContainer} - * to receive request messages and {@link AmqpTemplate} to send replies. - * @param listenerContainer the {@link MessageListenerContainer} to receive AMQP messages. - * @param amqpTemplate the {@link AmqpTemplate} to send reply messages. - */ - @SuppressWarnings("this-escape") - public AmqpInboundGateway(MessageListenerContainer listenerContainer, AmqpTemplate amqpTemplate) { - this(listenerContainer, amqpTemplate, true); - } - - @SuppressWarnings("this-escape") - private AmqpInboundGateway(MessageListenerContainer listenerContainer, AmqpTemplate amqpTemplate, - boolean amqpTemplateExplicitlySet) { - - Assert.notNull(listenerContainer, "listenerContainer must not be null"); - Assert.notNull(amqpTemplate, "'amqpTemplate' must not be null"); - Assert.isNull(listenerContainer.getMessageListener(), - "The listenerContainer provided to an AMQP inbound Gateway " + - "must not have a MessageListener configured since " + - "the adapter needs to configure its own listener implementation."); - this.messageListenerContainer = listenerContainer; - this.messageListenerContainer.setAutoStartup(false); - this.amqpTemplate = amqpTemplate; - this.amqpTemplateExplicitlySet = amqpTemplateExplicitlySet; - if (this.amqpTemplateExplicitlySet && this.amqpTemplate instanceof RabbitTemplate rabbitTemplate) { - this.templateMessageConverter = rabbitTemplate.getMessageConverter(); - } - setErrorMessageStrategy(new AmqpMessageHeaderErrorMessageStrategy()); - this.abstractListenerContainer = - listenerContainer instanceof AbstractMessageListenerContainer abstractMessageListenerContainer - ? abstractMessageListenerContainer - : null; - } - - /** - * Specify the {@link MessageConverter} to convert request and reply to/from {@link Message}. - * If the {@link #amqpTemplate} is explicitly set, this {@link MessageConverter} - * isn't populated there. You must configure that external {@link #amqpTemplate}. - * @param messageConverter the {@link MessageConverter} to use. - */ - public void setMessageConverter(MessageConverter messageConverter) { - Assert.notNull(messageConverter, "MessageConverter must not be null"); - this.amqpMessageConverter = messageConverter; - if (!this.amqpTemplateExplicitlySet) { - ((RabbitTemplate) this.amqpTemplate).setMessageConverter(messageConverter); - this.templateMessageConverter = messageConverter; - } - } - - public void setHeaderMapper(AmqpHeaderMapper headerMapper) { - Assert.notNull(headerMapper, "headerMapper must not be null"); - this.headerMapper = headerMapper; - } - - /** - * The {@code defaultReplyTo} address with the form - *
-	 * (exchange)/(routingKey)
-	 * 
- * or - *
-	 * (queueName)
-	 * 
- * if the request message doesn't have a {@code replyTo} property. - * The second form uses the default exchange ("") and the queue name as - * the routing key. - * @param defaultReplyTo the default {@code replyTo} address to use. - * @since 4.2 - * @see Address - */ - public void setDefaultReplyTo(String defaultReplyTo) { - this.defaultReplyTo = new Address(defaultReplyTo); - } - - /** - * Set a {@link RetryTemplate} to use for retrying a message delivery within the - * gateway. Unlike adding retry at the container level, this can be used with an - * {@code ErrorMessageSendingRecoverer} {@link RecoveryCallback} to publish to the - * error channel after retries are exhausted. You generally should not configure an - * error channel when using retry here, use a {@link RecoveryCallback} instead. - * @param retryTemplate the template. - * @since 4.3.10. - * @see #setRecoveryCallback(RecoveryCallback) - */ - public void setRetryTemplate(RetryTemplate retryTemplate) { - this.retryTemplate = retryTemplate; - } - - /** - * Set a {@link RecoveryCallback} when using retry within the gateway. - * Mutually exclusive with {@link #setMessageRecoverer(MessageRecoverer)}. - * @param recoveryCallback the callback. - * @since 4.3.10 - * @see #setRetryTemplate(RetryTemplate) - */ - public void setRecoveryCallback(RecoveryCallback recoveryCallback) { - this.recoveryCallback = recoveryCallback; - } - - /** - * Configure a {@link MessageRecoverer} for retry operations. - * A more AMQP-specific convenience instead of {@link #setRecoveryCallback(RecoveryCallback)}. - * @param messageRecoverer the {@link MessageRecoverer} to use. - * @since 5.5 - */ - public void setMessageRecoverer(MessageRecoverer messageRecoverer) { - this.messageRecoverer = messageRecoverer; - } - - /** - * Set a batching strategy to use when de-batching messages. - * Default is {@link SimpleBatchingStrategy}. - * @param batchingStrategy the strategy. - * @since 5.2 - */ - public void setBatchingStrategy(BatchingStrategy batchingStrategy) { - Assert.notNull(batchingStrategy, "'batchingStrategy' cannot be null"); - this.batchingStrategy = batchingStrategy; - } - - /** - * Set to true to bind the source message in the header named - * {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}. - * @param bindSourceMessage true to bind. - * @since 5.1.6 - */ - public void setBindSourceMessage(boolean bindSourceMessage) { - this.bindSourceMessage = bindSourceMessage; - } - - /** - * When mapping headers for the outbound (reply) message, determine whether the headers are - * mapped before the message is converted, or afterward. This only affects headers - * that might be added by the message converter. When false, the converter's headers - * win; when true, any headers added by the converter will be overridden (if the - * source message has a header that maps to those headers). You might wish to set this - * to true, for example, when using a - * {@link org.springframework.amqp.support.converter.SimpleMessageConverter} with a - * String payload that contains JSON; the converter will set the content type to - * {@code text/plain} which can be overridden to {@code application/json} by setting - * the {@link AmqpHeaders#CONTENT_TYPE} message header. Default: false. - * @param replyHeadersMappedLast true if reply headers are mapped after conversion. - * @since 5.1.9 - */ - public void setReplyHeadersMappedLast(boolean replyHeadersMappedLast) { - this.replyHeadersMappedLast = replyHeadersMappedLast; - } - - @Override - public String getComponentType() { - return "amqp:inbound-gateway"; - } - - @Override - protected void onInit() { - if (this.retryTemplate != null) { - Assert.state(getErrorChannel() == null, "Cannot have an 'errorChannel' property when a 'RetryTemplate' is " - + "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to " - + "send an error message when retries are exhausted"); - setupRecoveryCallbackIfAny(); - } - Listener messageListener = new Listener(); - this.messageListenerContainer.setupMessageListener(messageListener); - this.messageListenerContainer.afterPropertiesSet(); - if (!this.amqpTemplateExplicitlySet) { - ((RabbitTemplate) this.amqpTemplate).afterPropertiesSet(); - } - super.onInit(); - if (this.retryTemplate != null && getErrorChannel() != null) { - logger.warn("Usually, when using a RetryTemplate you should use an ErrorMessageSendingRecoverer and not " - + "provide an errorChannel. Using an errorChannel could defeat retry and will receive an error " - + "message for each delivery attempt."); - } - } - - private void setupRecoveryCallbackIfAny() { - MessageRecoverer messageRecovererToUse = this.messageRecoverer; - Assert.state(this.recoveryCallback == null || messageRecovererToUse == null, - "Only one of 'recoveryCallback' or 'messageRecoverer' may be provided, but not both"); - if (messageRecovererToUse != null) { - this.recoveryCallback = - (context, cause) -> { - Message messageToRecover = - (Message) context.getAttribute(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE); - if (messageToRecover != null) { - messageRecovererToUse.recover(messageToRecover, cause); - } - return null; - }; - } - } - - @Override - protected void doStart() { - super.doStart(); - this.messageListenerContainer.start(); - } - - @Override - protected void doStop() { - super.doStop(); - this.messageListenerContainer.stop(); - } - - /** - * If there's a retry template, it will set the attribute holder via the listener. If - * there's no retry template, but there's an error channel, we create a new attribute - * holder here. If an attribute holder exists (by either method), we set the - * attributes for use by the - * {@link org.springframework.integration.support.ErrorMessageStrategy}. - * @param amqpMessage the AMQP message to use. - * @param message the Spring Messaging message to use. - * @since 4.3.10 - */ - private void setAttributesIfNecessary(Message amqpMessage, - org.springframework.messaging.@Nullable Message message) { - - boolean needHolder = getErrorChannel() != null || this.retryTemplate != null; - if (needHolder) { - AttributeAccessor attributes = ATTRIBUTES_HOLDER.get(); - if (attributes == null) { - attributes = ErrorMessageUtils.getAttributeAccessor(null, null); - ATTRIBUTES_HOLDER.set(attributes); - } - attributes.setAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY, message); - attributes.setAttribute(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, amqpMessage); - } - } - - @Override - protected AttributeAccessor getErrorMessageAttributes(org.springframework.messaging.@Nullable Message message) { - AttributeAccessor attributes = ATTRIBUTES_HOLDER.get(); - if (attributes == null) { - return super.getErrorMessageAttributes(message); - } - else { - return attributes; - } - } - - protected class Listener implements ChannelAwareMessageListener { - - protected Listener() { - } - - @SuppressWarnings("unchecked") - @Override - public void onMessage(final Message message, @Nullable Channel channel) { - try { - if (AmqpInboundGateway.this.retryTemplate == null) { - - org.springframework.messaging.Message converted = convert(message, channel); - if (converted != null) { - process(message, converted); - } - } - else { - org.springframework.messaging.Message converted = convert(message, channel); - if (converted != null) { - try { - AmqpInboundGateway.this.retryTemplate.execute(() -> { - AtomicInteger deliveryAttempt = StaticMessageHeaderAccessor.getDeliveryAttempt(converted); - if (deliveryAttempt != null) { - deliveryAttempt.incrementAndGet(); - } - process(message, converted); - return null; - }); - } - catch (RetryException ex) { - if (AmqpInboundGateway.this.recoveryCallback != null) { - AmqpInboundGateway.this.recoveryCallback.recover(getErrorMessageAttributes(converted), - ex.getCause()); - } - else { - throw new ListenerExecutionFailedException( - "Failed handling message after '" + ex.getRetryCount() + "' retries", ex, - message); - } - } - } - } - } - finally { - ATTRIBUTES_HOLDER.remove(); - } - } - - private org.springframework.messaging.@Nullable Message convert( - Message message, @Nullable Channel channel) { - - Map headers; - Object payload; - AbstractMessageListenerContainer listenerContainerToCheck = AmqpInboundGateway.this.abstractListenerContainer; - boolean isManualAck = - listenerContainerToCheck != null - && AcknowledgeMode.MANUAL == listenerContainerToCheck.getAcknowledgeMode(); - try { - if (AmqpInboundGateway.this.batchingStrategy.canDebatch(message.getMessageProperties())) { - List payloads = new ArrayList<>(); - AmqpInboundGateway.this.batchingStrategy.deBatch(message, fragment -> payloads - .add(AmqpInboundGateway.this.amqpMessageConverter.fromMessage(fragment))); - payload = payloads; - } - else { - payload = AmqpInboundGateway.this.amqpMessageConverter.fromMessage(message); - } - headers = AmqpInboundGateway.this.headerMapper.toHeadersFromRequest(message.getMessageProperties()); - if (isManualAck) { - headers.put(AmqpHeaders.DELIVERY_TAG, message.getMessageProperties().getDeliveryTag()); - headers.put(AmqpHeaders.CHANNEL, channel); - } - if (AmqpInboundGateway.this.retryTemplate != null) { - headers.put(IntegrationMessageHeaderAccessor.DELIVERY_ATTEMPT, new AtomicInteger()); - } - if (AmqpInboundGateway.this.bindSourceMessage) { - headers.put(IntegrationMessageHeaderAccessor.SOURCE_DATA, message); - } - } - catch (RuntimeException e) { - MessageChannel errorChannel = getErrorChannel(); - if (errorChannel != null) { - setAttributesIfNecessary(message, null); - AmqpInboundGateway.this.messagingTemplate.send(errorChannel, - buildErrorMessage(null, - EndpointUtils.errorMessagePayload(message, channel, isManualAck, e))); - } - else { - throw e; - } - return null; - } - return getMessageBuilderFactory() - .withPayload(payload) - .copyHeaders(headers) - .build(); - } - - private void process(Message message, org.springframework.messaging.Message messagingMessage) { - setAttributesIfNecessary(message, messagingMessage); - org.springframework.messaging.Message reply = sendAndReceiveMessage(messagingMessage); - if (reply != null) { - Address replyTo; - String replyToProperty = message.getMessageProperties().getReplyTo(); - if (replyToProperty != null) { - replyTo = new Address(replyToProperty); - } - else { - replyTo = AmqpInboundGateway.this.defaultReplyTo; - } - - org.springframework.amqp.core.Message amqpMessage = - MappingUtils.mapReplyMessage(reply, AmqpInboundGateway.this.templateMessageConverter, - AmqpInboundGateway.this.headerMapper, - message.getMessageProperties().getReceivedDeliveryMode(), - AmqpInboundGateway.this.replyHeadersMappedLast); - - if (replyTo != null) { - AmqpInboundGateway.this.amqpTemplate.send(replyTo.getExchangeName(), replyTo.getRoutingKey(), - amqpMessage); - } - else { - if (!AmqpInboundGateway.this.amqpTemplateExplicitlySet) { - throw new IllegalStateException("There is no 'replyTo' message property " + - "and the `defaultReplyTo` hasn't been configured."); - } - else { - AmqpInboundGateway.this.amqpTemplate.send(amqpMessage); - } - } - } - } - - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpMessageSource.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpMessageSource.java deleted file mode 100644 index 52d139967ae..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpMessageSource.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.GetResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.batch.BatchingStrategy; -import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy; -import org.springframework.amqp.rabbit.connection.Connection; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.RabbitUtils; -import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter; -import org.springframework.amqp.rabbit.support.MessagePropertiesConverter; -import org.springframework.amqp.rabbit.support.RabbitExceptionTranslator; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.acks.AcknowledgmentCallback; -import org.springframework.integration.acks.AcknowledgmentCallbackFactory; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.AmqpMessageHeaderErrorMessageStrategy; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.endpoint.AbstractMessageSource; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.util.Assert; - -/** - * A pollable {@link org.springframework.integration.core.MessageSource} for RabbitMQ. - * - * @author Gary Russell - * - * @since 5.0.1 - * - */ -public class AmqpMessageSource extends AbstractMessageSource { - - private final String queue; - - private final ConnectionFactory connectionFactory; - - private final AmqpAckCallbackFactory ackCallbackFactory; - - private boolean transacted; - - private MessagePropertiesConverter propertiesConverter = new DefaultMessagePropertiesConverter(); - - private AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - - private MessageConverter messageConverter = new SimpleMessageConverter(); - - private boolean rawMessageHeader; - - private BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(0, 0, 0L); - - public AmqpMessageSource(ConnectionFactory connectionFactory, String queue) { - this(connectionFactory, new AmqpAckCallbackFactory(), queue); - } - - public AmqpMessageSource(ConnectionFactory connectionFactory, AmqpAckCallbackFactory ackCallbackFactory, - String queue) { - - Assert.notNull(connectionFactory, "'connectionFactory' cannot be null"); - Assert.notNull(ackCallbackFactory, "'ackCallbackFactory' cannot be null"); - Assert.notNull(queue, "'queue' cannot be null"); - this.connectionFactory = connectionFactory; - this.ackCallbackFactory = ackCallbackFactory; - this.queue = queue; - } - - protected boolean isTransacted() { - return this.transacted; - } - - /** - * Set to true to use a transacted channel for the ack. - * @param transacted true for transacted. - */ - public void setTransacted(boolean transacted) { - this.transacted = transacted; - } - - protected MessagePropertiesConverter getPropertiesConverter() { - return this.propertiesConverter; - } - - /** - * Set a custom {@link MessagePropertiesConverter} to replace the default - * {@link DefaultMessagePropertiesConverter}. - * @param propertiesConverter the converter. - */ - public void setPropertiesConverter(MessagePropertiesConverter propertiesConverter) { - this.propertiesConverter = propertiesConverter; - } - - protected AmqpHeaderMapper getHeaderMapper() { - return this.headerMapper; - } - - /** - * Set a custom {@link AmqpHeaderMapper} to replace the default - * {@link DefaultAmqpHeaderMapper#inboundMapper()}. - * @param headerMapper the header mapper. - */ - public void setHeaderMapper(AmqpHeaderMapper headerMapper) { - this.headerMapper = headerMapper; - } - - protected MessageConverter getMessageConverter() { - return this.messageConverter; - } - - /** - * Set a custom {@link MessageConverter} to replace the default - * {@link SimpleMessageConverter}. - * @param messageConverter the converter. - */ - public void setMessageConverter(MessageConverter messageConverter) { - this.messageConverter = messageConverter; - } - - protected boolean isRawMessageHeader() { - return this.rawMessageHeader; - } - - /** - * Set to true to include the raw spring-amqp message as a header with key - * {@link AmqpMessageHeaderErrorMessageStrategy#AMQP_RAW_MESSAGE}, enabling callers to - * have access to the message to process errors. The raw message is also added to the - * common header {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}. - * @param rawMessageHeader true to include the headers. - */ - public void setRawMessageHeader(boolean rawMessageHeader) { - this.rawMessageHeader = rawMessageHeader; - } - - protected BatchingStrategy getBatchingStrategy() { - return this.batchingStrategy; - } - - /** - * Set a batching strategy to use when de-batching messages. - * Default is {@link SimpleBatchingStrategy}. - * @param batchingStrategy the strategy. - * @since 5.2 - */ - public void setBatchingStrategy(BatchingStrategy batchingStrategy) { - Assert.notNull(batchingStrategy, "'batchingStrategy' cannot be null"); - this.batchingStrategy = batchingStrategy; - } - - @Override - public String getComponentType() { - return "amqp:message-source"; - } - - @Override - protected @Nullable AbstractIntegrationMessageBuilder doReceive() { - Connection connection = this.connectionFactory.createConnection(); // NOSONAR - RabbitUtils - Channel channel = connection.createChannel(this.transacted); - try { - GetResponse resp = channel.basicGet(this.queue, false); - if (resp == null) { - RabbitUtils.closeChannel(channel); - RabbitUtils.closeConnection(connection); - return null; - } - AcknowledgmentCallback callback = this.ackCallbackFactory - .createCallback(new AmqpAckInfo(connection, channel, this.transacted, resp)); - MessageProperties messageProperties = this.propertiesConverter.toMessageProperties(resp.getProps(), - resp.getEnvelope(), StandardCharsets.UTF_8.name()); - messageProperties.setConsumerQueue(this.queue); - Map headers = this.headerMapper.toHeadersFromRequest(messageProperties); - var amqpMessage = new org.springframework.amqp.core.Message(resp.getBody(), messageProperties); - Object payload; - if (this.batchingStrategy.canDebatch(messageProperties)) { - List payloads = new ArrayList<>(); - this.batchingStrategy.deBatch(amqpMessage, fragment -> payloads - .add(this.messageConverter.fromMessage(fragment))); - payload = payloads; - } - else { - payload = this.messageConverter.fromMessage(amqpMessage); - } - AbstractIntegrationMessageBuilder builder = getMessageBuilderFactory().withPayload(payload) - .copyHeaders(headers) - .setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, callback); - if (this.rawMessageHeader) { - builder.setHeader(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, amqpMessage); - builder.setHeader(IntegrationMessageHeaderAccessor.SOURCE_DATA, amqpMessage); - } - return builder; - } - catch (IOException e) { - RabbitUtils.closeChannel(channel); - RabbitUtils.closeConnection(connection); - throw RabbitExceptionTranslator.convertRabbitAccessException(e); - } - } - - public static class AmqpAckCallbackFactory implements AcknowledgmentCallbackFactory { - - @Override - public AcknowledgmentCallback createCallback(AmqpAckInfo info) { - return new AmqpAckCallback(info); - } - - } - - public static class AmqpAckCallback implements AcknowledgmentCallback { - - private static final Log LOGGER = LogFactory.getLog(AmqpAckCallback.class); - - private final AmqpAckInfo ackInfo; - - private boolean acknowledged; - - private boolean autoAckEnabled = true; - - public AmqpAckCallback(AmqpAckInfo ackInfo) { - this.ackInfo = ackInfo; - } - - protected AmqpAckInfo getAckInfo() { - return this.ackInfo; - } - - protected void setAcknowledged(boolean acknowledged) { - this.acknowledged = acknowledged; - } - - @Override - public boolean isAcknowledged() { - return this.acknowledged; - } - - @Override - public void noAutoAck() { - this.autoAckEnabled = false; - } - - @Override - public boolean isAutoAck() { - return this.autoAckEnabled; - } - - @Override - public void acknowledge(Status status) { - Assert.notNull(status, "'status' cannot be null"); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("acknowledge(" + status.name() + ") for " + this); - } - try { - long deliveryTag = this.ackInfo.getResponse().getEnvelope().getDeliveryTag(); - switch (status) { - case ACCEPT -> this.ackInfo.channel().basicAck(deliveryTag, false); - case REJECT -> this.ackInfo.channel().basicReject(deliveryTag, false); - case REQUEUE -> this.ackInfo.channel().basicReject(deliveryTag, true); - default -> { - } - } - if (this.ackInfo.transacted()) { - this.ackInfo.channel().txCommit(); - } - } - catch (IOException e) { - throw RabbitExceptionTranslator.convertRabbitAccessException(e); - } - finally { - RabbitUtils.closeChannel(this.ackInfo.channel()); - RabbitUtils.closeConnection(this.ackInfo.connection()); - this.acknowledged = true; - } - } - - @Override - public String toString() { - return "AmqpAckCallback [ackInfo=" + this.ackInfo + ", acknowledged=" + this.acknowledged - + ", autoAckEnabled=" + this.autoAckEnabled + "]"; - } - - } - - /** - * Information for building an AmqpAckCallback. - * @param connection the {@link Connection} to use - * @param channel the {@link Channel} to use - * @param transacted if channel is transacted - * @param getResponse the {@link GetResponse} to use - */ - public record AmqpAckInfo(Connection connection, Channel channel, boolean transacted, GetResponse getResponse) { - - @Deprecated(since = "7.0", forRemoval = true) - public Connection getConnection() { - return this.connection; - } - - @Deprecated(since = "7.0", forRemoval = true) - public Channel getChannel() { - return this.channel; - } - - @Deprecated(since = "7.0", forRemoval = true) - public boolean isTransacted() { - return this.transacted; - } - - @Deprecated(since = "7.0", forRemoval = true) - public GetResponse getGetResponse() { - return this.getResponse; - } - - @Override - public String toString() { - return "AmqpAckInfo [connection=" + this.connection + ", channel=" + this.channel + ", transacted=" - + this.transacted + ", getResponse=" + this.getResponse + "]"; - } - - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/package-info.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/package-info.java deleted file mode 100644 index 30fe1943a82..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting inbound endpoints. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.amqp.inbound; diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AbstractAmqpOutboundEndpoint.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AbstractAmqpOutboundEndpoint.java deleted file mode 100644 index 9a2c7c459e4..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AbstractAmqpOutboundEndpoint.java +++ /dev/null @@ -1,743 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.time.Duration; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.ReturnedMessage; -import org.springframework.amqp.rabbit.connection.Connection; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.expression.Expression; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.NackedAmqpMessageException; -import org.springframework.integration.amqp.support.ReturnedAmqpMessageException; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.integration.mapping.AbstractHeaderMapper; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.DefaultErrorMessageStrategy; -import org.springframework.integration.support.ErrorMessageStrategy; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A base {@link AbstractReplyProducingMessageHandler} extension for AMQP message handlers. - * - * @author Gary Russell - * @author Artem Bilan - * @author Christian Tzolov - * @author Ngoc Nhan - * - * @since 4.3 - * - */ -public abstract class AbstractAmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler - implements ManageableLifecycle { - - private static final String NO_ID = new UUID(0L, 0L).toString(); - - private final Lock lock = new ReentrantLock(); - - private AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - - private @Nullable String exchangeName; - - private @Nullable String routingKey; - - private @Nullable Expression exchangeNameExpression; - - private @Nullable Expression routingKeyExpression; - - private @Nullable ExpressionEvaluatingMessageProcessor routingKeyGenerator; - - private @Nullable ExpressionEvaluatingMessageProcessor exchangeNameGenerator; - - private @Nullable Expression confirmCorrelationExpression; - - private @Nullable ExpressionEvaluatingMessageProcessor correlationDataGenerator; - - private @Nullable MessageChannel confirmAckChannel; - - private @Nullable String confirmAckChannelName; - - private @Nullable MessageChannel confirmNackChannel; - - private @Nullable String confirmNackChannelName; - - private @Nullable MessageChannel returnChannel; - - private @Nullable MessageDeliveryMode defaultDeliveryMode; - - private boolean lazyConnect = true; - - private @Nullable ConnectionFactory connectionFactory; - - private @Nullable Expression delayExpression; - - private @Nullable ExpressionEvaluatingMessageProcessor delayGenerator; - - private boolean headersMappedLast; - - private @Nullable ErrorMessageStrategy errorMessageStrategy = new DefaultErrorMessageStrategy(); - - private @Nullable Duration confirmTimeout; - - private volatile boolean running; - - private volatile @Nullable ScheduledFuture confirmChecker; - - /** - * Set a custom {@link AmqpHeaderMapper} for mapping request and reply headers. - * Defaults to {@link DefaultAmqpHeaderMapper#outboundMapper()}. - * @param headerMapper the {@link AmqpHeaderMapper} to use. - */ - public void setHeaderMapper(AmqpHeaderMapper headerMapper) { - Assert.notNull(headerMapper, "headerMapper must not be null"); - this.headerMapper = headerMapper; - } - - /** - * When mapping headers for the outbound message, determine whether the headers are - * mapped before the message is converted, or afterwards. This only affects headers - * that might be added by the message converter. When false, the converter's headers - * win; when true, any headers added by the converter will be overridden (if the - * source message has a header that maps to those headers). You might wish to set this - * to true, for example, when using a - * {@link org.springframework.amqp.support.converter.SimpleMessageConverter} with a - * String payload that contains json; the converter will set the content type to - * {@code text/plain} which can be overridden to {@code application/json} by setting - * the {@link AmqpHeaders#CONTENT_TYPE} message header. Default: false. - * @param headersMappedLast true if headers are mapped after conversion. - * @since 5.0 - */ - public void setHeadersMappedLast(boolean headersMappedLast) { - this.headersMappedLast = headersMappedLast; - } - - /** - * Configure an AMQP exchange name for sending messages. - * @param exchangeName the exchange name for sending messages. - */ - public void setExchangeName(String exchangeName) { - Assert.notNull(exchangeName, "exchangeName must not be null"); - this.exchangeName = exchangeName; - } - - /** - * Configure a SpEL expression to evaluate an exchange name at runtime. - * @param exchangeNameExpression the expression to use. - * @since 4.3 - */ - public void setExchangeNameExpression(Expression exchangeNameExpression) { - this.exchangeNameExpression = exchangeNameExpression; - } - - /** - * @param exchangeNameExpression the String in SpEL syntax. - * @since 4.3 - */ - public void setExchangeNameExpressionString(String exchangeNameExpression) { - Assert.hasText(exchangeNameExpression, "'exchangeNameExpression' must not be empty"); - this.exchangeNameExpression = EXPRESSION_PARSER.parseExpression(exchangeNameExpression); - } - - /** - * Configure an AMQP routing key for sending messages. - * @param routingKey the routing key to use - */ - public void setRoutingKey(String routingKey) { - Assert.notNull(routingKey, "routingKey must not be null"); - this.routingKey = routingKey; - } - - /** - * @param routingKeyExpression the expression to use. - * @since 4.3 - */ - public void setRoutingKeyExpression(Expression routingKeyExpression) { - this.routingKeyExpression = routingKeyExpression; - } - - /** - * @param routingKeyExpression the String in SpEL syntax. - * @since 4.3 - */ - public void setRoutingKeyExpressionString(String routingKeyExpression) { - Assert.hasText(routingKeyExpression, "'routingKeyExpression' must not be empty"); - this.routingKeyExpression = EXPRESSION_PARSER.parseExpression(routingKeyExpression); - } - - /** - * Set a SpEL expression to evaluate confirm correlation at runtime. - * @param confirmCorrelationExpression the expression to use. - * @since 4.3 - */ - public void setConfirmCorrelationExpression(Expression confirmCorrelationExpression) { - this.confirmCorrelationExpression = confirmCorrelationExpression; - } - - /** - * Set a SpEL expression to evaluate confirm correlation at runtime. - * @param confirmCorrelationExpression the String in SpEL syntax. - * @since 4.3 - */ - public void setConfirmCorrelationExpressionString(String confirmCorrelationExpression) { - Assert.hasText(confirmCorrelationExpression, "'confirmCorrelationExpression' must not be empty"); - this.confirmCorrelationExpression = EXPRESSION_PARSER.parseExpression(confirmCorrelationExpression); - } - - /** - * Set the channel to which acks are send (publisher confirms). - * @param ackChannel the channel. - */ - public void setConfirmAckChannel(MessageChannel ackChannel) { - this.confirmAckChannel = ackChannel; - } - - /** - * Set the channel name to which acks are send (publisher confirms). - * @param ackChannelName the channel name. - * @since 4.3.12 - */ - public void setConfirmAckChannelName(String ackChannelName) { - this.confirmAckChannelName = ackChannelName; - } - - /** - * Set the channel to which nacks are send (publisher confirms). - * @param nackChannel the channel. - */ - public void setConfirmNackChannel(MessageChannel nackChannel) { - this.confirmNackChannel = nackChannel; - } - - /** - * Set the channel name to which nacks are send (publisher confirms). - * @param nackChannelName the channel name. - * @since 4.3.12 - */ - public void setConfirmNackChannelName(String nackChannelName) { - this.confirmNackChannelName = nackChannelName; - } - - /** - * Set the channel to which returned messages are sent. - * @param returnChannel the channel. - */ - public void setReturnChannel(MessageChannel returnChannel) { - this.returnChannel = returnChannel; - } - - /** - * Set the default delivery mode. - * @param defaultDeliveryMode the delivery mode. - */ - public void setDefaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) { - this.defaultDeliveryMode = defaultDeliveryMode; - } - - /** - * Set to {@code false} to attempt to connect during endpoint start; - * default {@code true}, meaning the connection will be attempted - * to be established on the arrival of the first message. - * @param lazyConnect the lazyConnect to set - * @since 4.1 - */ - public void setLazyConnect(boolean lazyConnect) { - this.lazyConnect = lazyConnect; - } - - /** - * Set the value to set in the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. By default, the {@link AmqpHeaders#DELAY} - * header (if present) is mapped; setting the delay here overrides that value. - * @param delay the delay. - * @since 4.3.5 - */ - public void setDelay(int delay) { - this.delayExpression = new ValueExpression<>(delay); - } - - /** - * Set the SpEL expression to calculate the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. By default, the {@link AmqpHeaders#DELAY} - * header (if present) is mapped; setting the expression here overrides that value. - * @param delayExpression the expression. - * @since 4.3.5 - */ - public void setDelayExpression(Expression delayExpression) { - this.delayExpression = delayExpression; - } - - /** - * Set the SpEL expression to calculate the {@code x-delay} header when using the - * RabbitMQ delayed message exchange plugin. By default, the {@link AmqpHeaders#DELAY} - * header (if present) is mapped; setting the expression here overrides that value. - * @param delayExpression the expression. - * @since 4.3.5 - */ - public void setDelayExpressionString(@Nullable String delayExpression) { - if (delayExpression == null) { - this.delayExpression = null; - } - else { - this.delayExpression = EXPRESSION_PARSER.parseExpression(delayExpression); - } - } - - /** - * Set the error message strategy to use for returned (or negatively confirmed) - * messages. - * @param errorMessageStrategy the strategy. - * @since 4.3.12 - */ - public void setErrorMessageStrategy(@Nullable ErrorMessageStrategy errorMessageStrategy) { - this.errorMessageStrategy = errorMessageStrategy; - } - - /** - * Set a timeout after which a nack will be synthesized if no publisher confirm has - * been received within that time. Missing confirms will be checked every 50% of this - * value so the synthesized nack will be sent between 1x and 1.5x this timeout. - * @param confirmTimeout the approximate timeout. - * @since 5.2 - * @see #setConfirmNackChannel(MessageChannel) - */ - public void setConfirmTimeout(long confirmTimeout) { - this.confirmTimeout = Duration.ofMillis(confirmTimeout); // NOSONAR sync inconsistency - } - - protected final void setConnectionFactory(ConnectionFactory connectionFactory) { - this.lock.lock(); - try { - this.connectionFactory = connectionFactory; - } - finally { - this.lock.unlock(); - } - } - - protected @Nullable String getExchangeName() { - return this.exchangeName; - } - - protected @Nullable String getRoutingKey() { - return this.routingKey; - } - - protected @Nullable Expression getExchangeNameExpression() { - return this.exchangeNameExpression; - } - - protected @Nullable Expression getRoutingKeyExpression() { - return this.routingKeyExpression; - } - - protected @Nullable ExpressionEvaluatingMessageProcessor getRoutingKeyGenerator() { - return this.routingKeyGenerator; - } - - protected @Nullable ExpressionEvaluatingMessageProcessor getExchangeNameGenerator() { - return this.exchangeNameGenerator; - } - - public AmqpHeaderMapper getHeaderMapper() { - return this.headerMapper; - } - - protected @Nullable Expression getConfirmCorrelationExpression() { - return this.confirmCorrelationExpression; - } - - protected @Nullable ExpressionEvaluatingMessageProcessor getCorrelationDataGenerator() { - return this.correlationDataGenerator; - } - - protected @Nullable MessageChannel getConfirmAckChannel() { - if (this.confirmAckChannel == null && this.confirmAckChannelName != null) { - this.confirmAckChannel = getChannelResolver().resolveDestination(this.confirmAckChannelName); - } - return this.confirmAckChannel; - } - - protected @Nullable MessageChannel getConfirmNackChannel() { - if (this.confirmNackChannel == null && this.confirmNackChannelName != null) { - this.confirmNackChannel = getChannelResolver().resolveDestination(this.confirmNackChannelName); - } - return this.confirmNackChannel; - } - - protected @Nullable MessageChannel getReturnChannel() { - return this.returnChannel; - } - - protected @Nullable MessageDeliveryMode getDefaultDeliveryMode() { - return this.defaultDeliveryMode; - } - - protected boolean isLazyConnect() { - return this.lazyConnect; - } - - protected boolean isHeadersMappedLast() { - return this.headersMappedLast; - } - - protected @Nullable Duration getConfirmTimeout() { - return this.confirmTimeout; - } - - @Override - protected final void doInit() { - BeanFactory beanFactory = getBeanFactory(); - configureExchangeNameGenerator(beanFactory); - configureRoutingKeyGenerator(beanFactory); - configureCorrelationDataGenerator(beanFactory); - configureDelayGenerator(beanFactory); - - endpointInit(); - - if (this.headerMapper instanceof AbstractHeaderMapper) { - ((AbstractHeaderMapper) this.headerMapper).setBeanClassLoader(getBeanClassLoader()); - } - } - - private void configureExchangeNameGenerator(BeanFactory beanFactory) { - Assert.state(this.exchangeNameExpression == null || this.exchangeName == null, - "Either an exchangeName or an exchangeNameExpression can be provided, but not both"); - if (this.exchangeNameExpression != null) { - this.exchangeNameGenerator = - new ExpressionEvaluatingMessageProcessor<>(this.exchangeNameExpression, String.class); - this.exchangeNameGenerator.setBeanFactory(beanFactory); - } - } - - private void configureRoutingKeyGenerator(BeanFactory beanFactory) { - Assert.state(this.routingKeyExpression == null || this.routingKey == null, - "Either a routingKey or a routingKeyExpression can be provided, but not both"); - if (this.routingKeyExpression != null) { - this.routingKeyGenerator = - new ExpressionEvaluatingMessageProcessor<>(this.routingKeyExpression, String.class); - this.routingKeyGenerator.setBeanFactory(beanFactory); - } - } - - private void configureCorrelationDataGenerator(BeanFactory beanFactory) { - if (this.confirmCorrelationExpression != null) { - this.correlationDataGenerator = - new ExpressionEvaluatingMessageProcessor<>(this.confirmCorrelationExpression, Object.class); - this.correlationDataGenerator.setBeanFactory(beanFactory); - } - else { - NullChannel nullChannel = extractTypeIfPossible(this.confirmAckChannel, NullChannel.class); - Assert.state( - (this.confirmAckChannel == null || nullChannel != null) && this.confirmAckChannelName == null, - "A 'confirmCorrelationExpression' is required when specifying a 'confirmAckChannel'"); - nullChannel = extractTypeIfPossible(this.confirmNackChannel, NullChannel.class); - Assert.state( - (this.confirmNackChannel == null || nullChannel != null) && this.confirmNackChannelName == null, - "A 'confirmCorrelationExpression' is required when specifying a 'confirmNackChannel'"); - } - } - - private void configureDelayGenerator(BeanFactory beanFactory) { - if (this.delayExpression != null) { - this.delayGenerator = new ExpressionEvaluatingMessageProcessor<>(this.delayExpression, Long.class); - this.delayGenerator.setBeanFactory(beanFactory); - } - } - - /** - * Subclasses can override to perform any additional initialization. - * Called from afterPropertiesSet(). - */ - protected void endpointInit() { - } - - @Override - public void start() { - this.lock.lock(); - try { - if (!this.running) { - if (!this.lazyConnect && this.connectionFactory != null) { - try { - Connection connection = this.connectionFactory.createConnection(); - connection.close(); - } - catch (RuntimeException ex) { - logger.error(ex, "Failed to eagerly establish the connection."); - } - } - doStart(); - Duration confirmTimeoutToUse = this.confirmTimeout; - if (confirmTimeoutToUse != null && getConfirmNackChannel() != null && getRabbitTemplate() != null) { - this.confirmChecker = getTaskScheduler() - .scheduleAtFixedRate(checkUnconfirmed(confirmTimeoutToUse), confirmTimeoutToUse.dividedBy(2L)); - } - this.running = true; - } - } - finally { - this.lock.unlock(); - } - - } - - private Runnable checkUnconfirmed(Duration confirmTimeoutToUse) { - return () -> { - RabbitTemplate rabbitTemplate = getRabbitTemplate(); - if (rabbitTemplate != null) { - Collection unconfirmed = - rabbitTemplate.getUnconfirmed(confirmTimeoutToUse.toMillis()); - if (unconfirmed != null) { - unconfirmed.forEach(correlation -> handleConfirm(correlation, false, "Confirm timed out")); - } - } - }; - } - - @Nullable - protected abstract RabbitTemplate getRabbitTemplate(); - - @Override - public void stop() { - this.lock.lock(); - try { - if (this.running) { - doStop(); - } - this.running = false; - ScheduledFuture confirmCheckerToCancel = this.confirmChecker; - if (confirmCheckerToCancel != null) { - confirmCheckerToCancel.cancel(false); - this.confirmChecker = null; - } - } - finally { - this.lock.unlock(); - } - } - - protected void doStart() { - } - - protected void doStop() { - } - - @Override - public boolean isRunning() { - return this.running; - } - - protected @Nullable CorrelationData generateCorrelationData(Message requestMessage) { - CorrelationData correlationData = null; - UUID uuid = requestMessage.getHeaders().getId(); - String messageId; - if (uuid == null) { - messageId = NO_ID; - } - else { - messageId = uuid.toString(); - } - if (this.correlationDataGenerator != null) { - Object userData = this.correlationDataGenerator.processMessage(requestMessage); - if (userData != null) { - correlationData = new CorrelationDataWrapper(messageId, userData, requestMessage); - } - else { - this.logger.debug("'confirmCorrelationExpression' resolved to 'null'; " - + "no publisher confirm will be sent to the ack or nack channel"); - } - } - if (correlationData == null) { - Object correlation = requestMessage.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM_CORRELATION); - if (correlation instanceof CorrelationData castCorrelationData) { - correlationData = castCorrelationData; - } - if (correlationData != null) { - correlationData = new CorrelationDataWrapper(messageId, correlationData, requestMessage); - } - } - return correlationData; - } - - protected @Nullable String generateExchangeName(Message requestMessage) { - String exchange = this.exchangeName; - if (this.exchangeNameGenerator != null) { - exchange = this.exchangeNameGenerator.processMessage(requestMessage); - } - return exchange; - } - - protected @Nullable String generateRoutingKey(Message requestMessage) { - String key = this.routingKey; - if (this.routingKeyGenerator != null) { - key = this.routingKeyGenerator.processMessage(requestMessage); - } - return key; - } - - protected void addDelayProperty(Message message, org.springframework.amqp.core.Message amqpMessage) { - if (this.delayGenerator != null) { - amqpMessage.getMessageProperties().setDelayLong(this.delayGenerator.processMessage(message)); - } - } - - protected AbstractIntegrationMessageBuilder buildReply(MessageConverter converter, - org.springframework.amqp.core.Message amqpReplyMessage) { - - Object replyObject = converter.fromMessage(amqpReplyMessage); - AbstractIntegrationMessageBuilder builder = prepareMessageBuilder(replyObject); - Map headers = getHeaderMapper().toHeadersFromReply(amqpReplyMessage.getMessageProperties()); - builder.copyHeadersIfAbsent(headers); - return builder; - } - - private AbstractIntegrationMessageBuilder prepareMessageBuilder(Object replyObject) { - return replyObject instanceof Message - ? getMessageBuilderFactory().fromMessage((Message) replyObject) - : getMessageBuilderFactory().withPayload(replyObject); - } - - protected Message buildReturnedMessage(ReturnedMessage returnedMessage, MessageConverter converter) { - org.springframework.amqp.core.Message amqpMessage = returnedMessage.getMessage(); - Object returnedObject = converter.fromMessage(amqpMessage); - AbstractIntegrationMessageBuilder builder = prepareMessageBuilder(returnedObject); - Map headers = getHeaderMapper().toHeadersFromReply(amqpMessage.getMessageProperties()); - if (this.errorMessageStrategy == null) { - builder.copyHeadersIfAbsent(headers) - .setHeader(AmqpHeaders.RETURN_REPLY_CODE, returnedMessage.getReplyCode()) - .setHeader(AmqpHeaders.RETURN_REPLY_TEXT, returnedMessage.getReplyText()) - .setHeader(AmqpHeaders.RETURN_EXCHANGE, returnedMessage.getExchange()) - .setHeader(AmqpHeaders.RETURN_ROUTING_KEY, returnedMessage.getRoutingKey()); - } - Message message = builder.build(); - if (this.errorMessageStrategy != null) { - message = this.errorMessageStrategy.buildErrorMessage(new ReturnedAmqpMessageException( - message, amqpMessage, returnedMessage.getReplyCode(), returnedMessage.getReplyText(), - returnedMessage.getExchange(), returnedMessage.getRoutingKey()), null); - } - return message; - } - - protected void handleConfirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause) { - if (correlationData == null) { - logger.debug(() -> "No correlation data provided for ack: " + ack + " cause:" + cause); - return; - } - CorrelationDataWrapper wrapper = (CorrelationDataWrapper) correlationData; - Object userCorrelationData = wrapper.getUserData(); - MessageChannel ackChannel = getConfirmAckChannel(); - if (ack && ackChannel != null) { - sendOutput(buildConfirmMessage(ack, cause, wrapper, userCorrelationData), ackChannel, true); - } - else { - MessageChannel nackChannel = getConfirmNackChannel(); - if (!ack && nackChannel != null) { - sendOutput(buildConfirmMessage(ack, cause, wrapper, userCorrelationData), nackChannel, true); - } - else { - logger.debug(() -> "Nowhere to send publisher confirm " + (ack ? "ack" : "nack") + " for " - + userCorrelationData); - } - } - } - - private Message buildConfirmMessage(boolean ack, @Nullable String cause, CorrelationDataWrapper wrapper, - Object userCorrelationData) { - - if (this.errorMessageStrategy == null || ack) { - Map headers = new HashMap<>(); - headers.put(AmqpHeaders.PUBLISH_CONFIRM, ack); - if (!ack && StringUtils.hasText(cause)) { - headers.put(AmqpHeaders.PUBLISH_CONFIRM_NACK_CAUSE, cause); - } - - return prepareMessageBuilder(userCorrelationData) - .copyHeaders(headers) - .build(); - } - else { - return this.errorMessageStrategy.buildErrorMessage(new NackedAmqpMessageException(wrapper.getMessage(), - wrapper.getUserData(), Objects.requireNonNull(cause)), null); - } - } - - protected static final class CorrelationDataWrapper extends CorrelationData { - - private final Object userData; - - private final Message message; - - CorrelationDataWrapper(String id, Object userData, Message message) { - super(id); - this.userData = userData; - this.message = message; - } - - public Object getUserData() { - return this.userData; - } - - public Message getMessage() { - return this.message; - } - - @Override - public CompletableFuture getFuture() { - if (this.userData instanceof CorrelationData correlationData) { - return correlationData.getFuture(); - } - else { - return super.getFuture(); - } - } - - @Override - public void setReturned(ReturnedMessage returned) { - if (this.userData instanceof CorrelationData correlationData) { - correlationData.setReturned(returned); - } - super.setReturned(returned); - } - - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java deleted file mode 100644 index 97d6b4dcc87..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.time.Duration; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.AmqpException; -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.ReturnedMessage; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.connection.CorrelationData.Confirm; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.context.Lifecycle; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.MessageTimeoutException; -import org.springframework.integration.amqp.support.MappingUtils; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Adapter that converts and sends Messages to an AMQP Exchange. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 2.1 - */ -public class AmqpOutboundEndpoint extends AbstractAmqpOutboundEndpoint - implements ConfirmCallback, RabbitTemplate.ReturnsCallback { - - private static final Duration DEFAULT_CONFIRM_TIMEOUT = Duration.ofSeconds(5); - - private final AmqpTemplate amqpTemplate; - - private final @Nullable RabbitTemplate rabbitTemplate; - - private boolean expectReply; - - private boolean waitForConfirm; - - private Duration waitForConfirmTimeout = DEFAULT_CONFIRM_TIMEOUT; - - private boolean multiSend; - - @SuppressWarnings("this-escape") - public AmqpOutboundEndpoint(AmqpTemplate amqpTemplate) { - Assert.notNull(amqpTemplate, "amqpTemplate must not be null"); - this.amqpTemplate = amqpTemplate; - if (amqpTemplate instanceof RabbitTemplate castRabbitTemplate) { - setConnectionFactory(castRabbitTemplate.getConnectionFactory()); - this.rabbitTemplate = castRabbitTemplate; - } - else { - this.rabbitTemplate = null; - } - } - - /** - * Set to true if this endpoint is a gateway. - * @param expectReply true for a gateway. - */ - public void setExpectReply(boolean expectReply) { - this.expectReply = expectReply; - } - - /** - * Set to true if you want to block the calling thread until a publisher confirm has - * been received. Requires a template configured for returns. If a confirm is not - * received within the confirm timeout or a negative acknowledgment or returned - * message is received, an exception will be thrown. Does not apply to the gateway - * since it blocks awaiting the reply. - * @param waitForConfirm true to block until the confirmation or timeout is received. - * @since 5.2 - * @see #setConfirmTimeout(long) - * @see #setMultiSend(boolean) - */ - public void setWaitForConfirm(boolean waitForConfirm) { - this.waitForConfirm = waitForConfirm; - } - - @Override - public String getComponentType() { - return this.expectReply ? "amqp:outbound-gateway" : "amqp:outbound-channel-adapter"; - } - - /** - * If true, and the message payload is an {@link Iterable} of {@link Message}, send - * the messages in a single invocation of the template (same channel) and optionally - * wait for the confirms or die or perform all sends within a transaction (existing or - * new). - * @param multiSend true to send multiple messages. - * @since 5.3 - * @see #setWaitForConfirm(boolean) - */ - public void setMultiSend(boolean multiSend) { - Assert.isTrue(this.rabbitTemplate != null - && (!this.waitForConfirm || this.rabbitTemplate.getConnectionFactory().isSimplePublisherConfirms()), - () -> "To use multiSend, " + AmqpOutboundEndpoint.this.amqpTemplate - + " must be a RabbitTemplate with a ConnectionFactory configured with simple confirms"); - this.multiSend = multiSend; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return this.expectReply ? super.getIntegrationPatternType() : IntegrationPatternType.outbound_channel_adapter; - } - - @Override - public @Nullable RabbitTemplate getRabbitTemplate() { - return this.rabbitTemplate; - } - - @Override - protected void endpointInit() { - if (getConfirmCorrelationExpression() != null) { - Assert.notNull(this.rabbitTemplate, - "RabbitTemplate implementation is required for publisher confirms"); - this.rabbitTemplate.setConfirmCallback(this); - if (!this.rabbitTemplate.getConnectionFactory().isPublisherConfirms()) { - this.logger.warn("A confirm correlation expression is provided but the underlying connection factory " - + "does not support correlated delivery confirmations; no confirmations will be received"); - } - } - if (getReturnChannel() != null) { - Assert.notNull(this.rabbitTemplate, - "RabbitTemplate implementation is required for publisher confirms"); - this.rabbitTemplate.setReturnsCallback(this); - if (!this.rabbitTemplate.getConnectionFactory().isPublisherReturns()) { - this.logger.warn("A return channel is provided but the underlying connection factory " - + "does not support returned messages; none will be received"); - } - } - Duration confirmTimeout = getConfirmTimeout(); - if (confirmTimeout != null) { - this.waitForConfirmTimeout = confirmTimeout; - } - } - - @Override - protected void doStop() { - if (this.amqpTemplate instanceof Lifecycle lifecycle) { - lifecycle.stop(); - } - } - - @Override - protected @Nullable Object handleRequestMessage(Message requestMessage) { - CorrelationData correlationData = generateCorrelationData(requestMessage); - String exchangeName = generateExchangeName(requestMessage); - String routingKey = generateRoutingKey(requestMessage); - if (this.expectReply) { - return sendAndReceive(exchangeName, routingKey, requestMessage, correlationData); - } - if (this.multiSend && requestMessage.getPayload() instanceof Iterable) { - multiSend(requestMessage, exchangeName, routingKey); - return null; - } - else { - send(exchangeName, routingKey, requestMessage, correlationData); - if (this.waitForConfirm && correlationData != null) { - waitForConfirm(requestMessage, correlationData); - } - return null; - } - } - - @SuppressWarnings("unchecked") - private void multiSend(Message requestMessage, @Nullable String exchangeName, @Nullable String routingKey) { - ((Iterable) requestMessage.getPayload()).forEach(payload -> { - Assert.state(payload instanceof Message, - "To use multiSend, the payload must be an Iterable>"); - }); - RabbitTemplate rabbitTemplateToUse = this.rabbitTemplate; - Assert.notNull(rabbitTemplateToUse, "The 'RabbitTemplate' must be provided for multi-send."); - rabbitTemplateToUse.invoke(template -> { - ((Iterable>) requestMessage.getPayload()) - .forEach(message -> doRabbitSend(exchangeName, routingKey, message, null, rabbitTemplateToUse)); - if (this.waitForConfirm) { - template.waitForConfirmsOrDie(this.waitForConfirmTimeout.toMillis()); - } - return null; - }); - } - - private void waitForConfirm(Message requestMessage, CorrelationData correlationData) { - try { - Confirm confirm = correlationData.getFuture().get(this.waitForConfirmTimeout.toMillis(), - TimeUnit.MILLISECONDS); - if (!confirm.ack()) { - throw new AmqpException("Negative publisher confirm received: " + confirm); - } - if (correlationData.getReturned() != null) { - throw new AmqpException("Message was returned by the broker"); - } - } - catch (@SuppressWarnings("unused") InterruptedException e) { - Thread.currentThread().interrupt(); - } - catch (ExecutionException e) { - throw new AmqpException("Failed to get publisher confirm", e); - } - catch (TimeoutException e) { - throw new MessageTimeoutException(requestMessage, this + ": Timed out awaiting publisher confirm", e); - } - } - - private void send(@Nullable String exchangeName, @Nullable String routingKey, - final Message requestMessage, @Nullable CorrelationData correlationData) { - - if (this.rabbitTemplate != null) { - doRabbitSend(exchangeName, routingKey, requestMessage, correlationData, this.rabbitTemplate); - } - else { - this.amqpTemplate.convertAndSend(exchangeName, routingKey, requestMessage.getPayload(), - message -> { - getHeaderMapper().fromHeadersToRequest(requestMessage.getHeaders(), - message.getMessageProperties()); - return message; - }); - } - } - - private void doRabbitSend(@Nullable String exchangeName, @Nullable String routingKey, Message requestMessage, - @Nullable CorrelationData correlationData, RabbitTemplate template) { - - MessageConverter converter = template.getMessageConverter(); - org.springframework.amqp.core.Message amqpMessage = MappingUtils.mapMessage(requestMessage, converter, - getHeaderMapper(), getDefaultDeliveryMode(), isHeadersMappedLast()); - addDelayProperty(requestMessage, amqpMessage); - template.send(exchangeName, routingKey, amqpMessage, correlationData); - } - - private @Nullable AbstractIntegrationMessageBuilder sendAndReceive(@Nullable String exchangeName, - @Nullable String routingKey, Message requestMessage, @Nullable CorrelationData correlationData) { - - Assert.state(this.rabbitTemplate != null, "RabbitTemplate implementation is required for publisher confirms"); - MessageConverter converter = this.rabbitTemplate.getMessageConverter(); - org.springframework.amqp.core.Message amqpMessage = MappingUtils.mapMessage(requestMessage, converter, - getHeaderMapper(), getDefaultDeliveryMode(), isHeadersMappedLast()); - addDelayProperty(requestMessage, amqpMessage); - org.springframework.amqp.core.Message amqpReplyMessage = - this.rabbitTemplate.sendAndReceive(exchangeName, routingKey, amqpMessage, - correlationData); - - if (amqpReplyMessage == null) { - return null; - } - return buildReply(converter, amqpReplyMessage); - } - - @Override - public void confirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause) { - handleConfirm(correlationData, ack, cause); - } - - @Override - public void returnedMessage(ReturnedMessage returnedMessage) { - Assert.state(this.rabbitTemplate != null, "RabbitTemplate implementation is required for publisher confirms"); - MessageConverter converter = this.rabbitTemplate.getMessageConverter(); - Message returned = buildReturnedMessage(returnedMessage, converter); - Objects.requireNonNull(getReturnChannel()).send(returned); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AsyncAmqpOutboundGateway.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AsyncAmqpOutboundGateway.java deleted file mode 100644 index d8e1827954a..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AsyncAmqpOutboundGateway.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.util.function.BiConsumer; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.AmqpMessageReturnedException; -import org.springframework.amqp.core.AmqpReplyTimeoutException; -import org.springframework.amqp.core.ReturnedMessage; -import org.springframework.amqp.rabbit.AsyncRabbitTemplate; -import org.springframework.amqp.rabbit.RabbitMessageFuture; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.connection.CorrelationData.Confirm; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.integration.amqp.support.MappingUtils; -import org.springframework.integration.handler.ReplyRequiredException; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.MessagingException; -import org.springframework.util.Assert; - -/** - * An outbound gateway where the sending thread is released immediately and the reply - * is sent on the async template's listener container thread. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -public class AsyncAmqpOutboundGateway extends AbstractAmqpOutboundEndpoint { - - private final AsyncRabbitTemplate template; - - private final MessageConverter messageConverter; - - @SuppressWarnings("this-escape") - public AsyncAmqpOutboundGateway(AsyncRabbitTemplate template) { - Assert.notNull(template, "AsyncRabbitTemplate cannot be null"); - this.template = template; - this.messageConverter = template.getMessageConverter(); - Assert.notNull(this.messageConverter, "the template's message converter cannot be null"); - setConnectionFactory(this.template.getConnectionFactory()); - setAsync(true); - } - - @Override - public String getComponentType() { - return "amqp:outbound-async-gateway"; - } - - @Override - protected RabbitTemplate getRabbitTemplate() { - return this.template.getRabbitTemplate(); - } - - @Override - protected void doStart() { - super.doStart(); - this.template.start(); - } - - @Override - protected void doStop() { - this.template.stop(); - super.doStop(); - } - - @Override - protected @Nullable Object handleRequestMessage(Message requestMessage) { - org.springframework.amqp.core.Message amqpMessage = MappingUtils.mapMessage(requestMessage, - this.messageConverter, getHeaderMapper(), getDefaultDeliveryMode(), isHeadersMappedLast()); - addDelayProperty(requestMessage, amqpMessage); - RabbitMessageFuture future = this.template.sendAndReceive(generateExchangeName(requestMessage), - generateRoutingKey(requestMessage), amqpMessage); - CorrelationData correlationData = generateCorrelationData(requestMessage); - if (correlationData != null) { - future.getConfirm().whenComplete(new CorrelationCallback(correlationData, future)); - } - future.whenComplete(new FutureCallback(requestMessage, correlationData)); - return null; - } - - private final class FutureCallback implements BiConsumer { - - private final Message requestMessage; - - private final @Nullable CorrelationDataWrapper correlationData; - - FutureCallback(Message requestMessage, @Nullable CorrelationData correlationData) { - this.requestMessage = requestMessage; - this.correlationData = (CorrelationDataWrapper) correlationData; - } - - @Override - public void accept(org.springframework.amqp.core.Message message, @Nullable Throwable throwable) { - if (throwable == null) { - AbstractIntegrationMessageBuilder replyMessageBuilder = null; - try { - replyMessageBuilder = buildReply(AsyncAmqpOutboundGateway.this.messageConverter, message); - sendOutputs(replyMessageBuilder, this.requestMessage); - } - catch (Exception ex) { - Exception exceptionToLogAndSend = ex; - if (!(ex instanceof MessagingException)) { // NOSONAR - exceptionToLogAndSend = new MessageHandlingException(this.requestMessage, - "failed to handle a message in the [" + AsyncAmqpOutboundGateway.this + ']', ex); - if (replyMessageBuilder != null) { - exceptionToLogAndSend = - new MessagingException(replyMessageBuilder.build(), exceptionToLogAndSend); - } - } - logger.error(exceptionToLogAndSend, () -> "Failed to send async reply: " + message); - sendErrorMessage(this.requestMessage, exceptionToLogAndSend); - } - } - else { - Throwable exceptionToSend = throwable; - if (throwable instanceof AmqpReplyTimeoutException) { - if (getRequiresReply()) { - exceptionToSend = - new ReplyRequiredException(this.requestMessage, "Timeout on async request/reply", - throwable); - } - else { - logger.debug(() -> "Reply not required and async timeout for " + this.requestMessage); - return; - } - } - if (throwable instanceof AmqpMessageReturnedException amre) { - MessageChannel returnChannel = getReturnChannel(); - if (returnChannel != null) { - Message returnedMessage = buildReturnedMessage( - new ReturnedMessage(amre.getReturnedMessage(), amre.getReplyCode(), amre.getReplyText(), - amre.getExchange(), amre.getRoutingKey()), - AsyncAmqpOutboundGateway.this.messageConverter); - sendOutput(returnedMessage, returnChannel, true); - } - if (this.correlationData != null) { - this.correlationData.setReturned(amre.getReturned()); - /* - * Complete the user's future (if present) since the async template will only complete - * once, successfully, or with a failure. - */ - this.correlationData.getFuture().complete(new Confirm(true, null)); - } - } - else { - sendErrorMessage(this.requestMessage, exceptionToSend); - } - } - } - - } - - private final class CorrelationCallback implements BiConsumer { - - private final CorrelationData correlationData; - - private final RabbitMessageFuture replyFuture; - - CorrelationCallback(CorrelationData correlationData, RabbitMessageFuture replyFuture) { - this.correlationData = correlationData; - this.replyFuture = replyFuture; - } - - @Override - public void accept(@Nullable Boolean result, Throwable throwable) { - if (result != null) { - try { - handleConfirm(this.correlationData, result, this.replyFuture.getNackCause()); - } - catch (Exception e) { - logger.error("Failed to send publisher confirm"); - } - } - } - - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/RabbitStreamMessageHandler.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/RabbitStreamMessageHandler.java deleted file mode 100644 index ec21b52915e..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/RabbitStreamMessageHandler.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.MappingUtils; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.core.MessagingTemplate; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.rabbit.stream.producer.RabbitStreamOperations; -import org.springframework.rabbit.stream.support.StreamMessageProperties; -import org.springframework.util.Assert; - -/** - * {@link MessageHandler} based on {@link RabbitStreamOperations}. - * - * @author Gary Russell - * @author Chris Bono - * @author Ryan Riley - * @since 6.0 - * - */ -public class RabbitStreamMessageHandler extends AbstractMessageHandler { - - private static final int DEFAULT_CONFIRM_TIMEOUT = 10_000; - - private final RabbitStreamOperations streamOperations; - - private final MessagingTemplate messagingTemplate = new MessagingTemplate(); - - private boolean sync; - - private long confirmTimeout = DEFAULT_CONFIRM_TIMEOUT; - - private @Nullable MessageChannel sendFailureChannel; - - private @Nullable String sendFailureChannelName; - - private @Nullable MessageChannel sendSuccessChannel; - - private @Nullable String sendSuccessChannelName; - - private AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - - private boolean headersMappedLast; - - /** - * Create an instance with the provided {@link RabbitStreamOperations}. - * @param streamOperations the operations. - */ - public RabbitStreamMessageHandler(RabbitStreamOperations streamOperations) { - Assert.notNull(streamOperations, "'streamOperations' cannot be null"); - this.streamOperations = streamOperations; - } - - /** - * Set the failure channel. After a send failure, an - * {@link org.springframework.messaging.support.ErrorMessage} will be sent - * to this channel with a payload of the exception with the - * failed message. - * @param sendFailureChannel the failure channel. - */ - public void setSendFailureChannel(MessageChannel sendFailureChannel) { - this.sendFailureChannel = sendFailureChannel; - } - - /** - * Set the failure channel name. After a send failure, an - * {@link org.springframework.messaging.support.ErrorMessage} will be sent - * to this channel with a payload of the exception with the - * failed message. - * @param sendFailureChannelName the failure channel name. - */ - public void setSendFailureChannelName(String sendFailureChannelName) { - this.sendFailureChannelName = sendFailureChannelName; - } - - /** - * Set the success channel. - * @param sendSuccessChannel the success channel. - */ - public void setSendSuccessChannel(MessageChannel sendSuccessChannel) { - this.sendSuccessChannel = sendSuccessChannel; - } - - /** - * Set the Success channel name. - * @param sendSuccessChannelName the success channel name. - */ - public void setSendSuccessChannelName(String sendSuccessChannelName) { - this.sendSuccessChannelName = sendSuccessChannelName; - } - - /** - * Set to true to wait for a confirmation. - * @param sync true to wait. - * @see #setConfirmTimeout(long) - */ - public void setSync(boolean sync) { - this.sync = sync; - } - - /** - * Set the confirm timeout. - * @param confirmTimeout the timeout. - * @see #setSync(boolean) - */ - public void setConfirmTimeout(long confirmTimeout) { - this.confirmTimeout = confirmTimeout; - } - - /** - * Set a custom {@link AmqpHeaderMapper} for mapping request and reply headers. - * Defaults to {@link DefaultAmqpHeaderMapper#outboundMapper()}. - * @param headerMapper the {@link AmqpHeaderMapper} to use. - */ - public void setHeaderMapper(AmqpHeaderMapper headerMapper) { - Assert.notNull(headerMapper, "headerMapper must not be null"); - this.headerMapper = headerMapper; - } - - /** - * When mapping headers for the outbound message, determine whether the headers are - * mapped before the message is converted, or afterwards. This only affects headers - * that might be added by the message converter. When false, the converter's headers - * win; when true, any headers added by the converter will be overridden (if the - * source message has a header that maps to those headers). You might wish to set this - * to true, for example, when using a - * {@link org.springframework.amqp.support.converter.SimpleMessageConverter} with a - * String payload that contains json; the converter will set the content type to - * {@code text/plain} which can be overridden to {@code application/json} by setting - * the {@link AmqpHeaders#CONTENT_TYPE} message header. Default: false. - * @param headersMappedLast true if headers are mapped after conversion. - */ - public void setHeadersMappedLast(boolean headersMappedLast) { - this.headersMappedLast = headersMappedLast; - } - - /** - * Return the {@link RabbitStreamOperations}. - * @return the operations. - */ - public RabbitStreamOperations getStreamOperations() { - return this.streamOperations; - } - - protected @Nullable MessageChannel getSendFailureChannel() { - if (this.sendFailureChannel == null && (this.sendFailureChannelName != null || !this.sync)) { - String sendFailureChannelNameToUse = this.sendFailureChannelName; - if (sendFailureChannelNameToUse == null) { - sendFailureChannelNameToUse = IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME; - } - this.sendFailureChannel = getChannelResolver().resolveDestination(sendFailureChannelNameToUse); - } - return this.sendFailureChannel; - } - - protected @Nullable MessageChannel getSendSuccessChannel() { - if (this.sendSuccessChannel != null) { - return this.sendSuccessChannel; - } - else if (this.sendSuccessChannelName != null) { - this.sendSuccessChannel = getChannelResolver().resolveDestination(this.sendSuccessChannelName); - return this.sendSuccessChannel; - } - return null; - } - - @Override - protected void handleMessageInternal(Message requestMessage) { - CompletableFuture future; - com.rabbitmq.stream.Message streamMessage; - if (requestMessage.getPayload() instanceof com.rabbitmq.stream.Message) { - streamMessage = (com.rabbitmq.stream.Message) requestMessage.getPayload(); - } - else { - MessageConverter converter = this.streamOperations.messageConverter(); - org.springframework.amqp.core.Message amqpMessage = mapMessage(requestMessage, converter, - this.headerMapper, this.headersMappedLast); - streamMessage = this.streamOperations.streamMessageConverter().fromMessage(amqpMessage); - } - future = this.streamOperations.send(streamMessage); - handleConfirms(requestMessage, future); - } - - private void handleConfirms(Message message, CompletableFuture future) { - future.whenComplete((bool, ex) -> { - if (ex != null) { - MessageChannel failures = getSendFailureChannel(); - if (failures != null) { - this.messagingTemplate.send(failures, new ErrorMessage(ex, message)); - } - } - else { - MessageChannel successes = getSendSuccessChannel(); - if (successes != null) { - this.messagingTemplate.send(successes, message); - } - } - }); - if (this.sync) { - try { - future.get(this.confirmTimeout, TimeUnit.MILLISECONDS); - } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - throw new MessageHandlingException(message, ex); - } - catch (ExecutionException | TimeoutException ex) { - throw new MessageHandlingException(message, ex); - } - } - } - - private static org.springframework.amqp.core.Message mapMessage(Message message, - MessageConverter converter, AmqpHeaderMapper headerMapper, boolean headersMappedLast) { - - MessageProperties amqpMessageProperties = new StreamMessageProperties(); - return MappingUtils.mapMessage(message, converter, headerMapper, headersMappedLast, headersMappedLast, - amqpMessageProperties); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/package-info.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/package-info.java deleted file mode 100644 index 3e94d4d6213..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting outbound endpoints. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.amqp.outbound; diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/AmqpHeaderMapper.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/AmqpHeaderMapper.java deleted file mode 100644 index 6dd74afb618..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/AmqpHeaderMapper.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import org.springframework.amqp.core.MessageProperties; -import org.springframework.integration.mapping.RequestReplyHeaderMapper; - -/** - * A convenience interface that extends {@link RequestReplyHeaderMapper}, - * parameterized with {@link MessageProperties}. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @since 2.1 - */ -public interface AmqpHeaderMapper extends RequestReplyHeaderMapper { - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/AmqpMessageHeaderErrorMessageStrategy.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/AmqpMessageHeaderErrorMessageStrategy.java deleted file mode 100644 index 2c67e8acdf6..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/AmqpMessageHeaderErrorMessageStrategy.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.util.HashMap; -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.core.AttributeAccessor; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.support.ErrorMessageStrategy; -import org.springframework.integration.support.ErrorMessageUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.ErrorMessage; - -/** - * An {@link ErrorMessageStrategy} extension that adds the raw AMQP message as - * a header to the {@link ErrorMessage}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3.10 - * - */ -public class AmqpMessageHeaderErrorMessageStrategy implements ErrorMessageStrategy { - - /** - * Header name/retry context variable for the raw received message. - */ - public static final String AMQP_RAW_MESSAGE = AmqpHeaders.PREFIX + "raw_message"; - - @Override - public ErrorMessage buildErrorMessage(Throwable throwable, @Nullable AttributeAccessor context) { - Object inputMessage = context == null ? null - : context.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY); - Map headers = new HashMap<>(); - if (context != null) { - Object amqpRawMessage = context.getAttribute(AMQP_RAW_MESSAGE); - if (amqpRawMessage != null) { - headers.put(AMQP_RAW_MESSAGE, amqpRawMessage); - headers.put(IntegrationMessageHeaderAccessor.SOURCE_DATA, amqpRawMessage); - } - } - if (inputMessage instanceof Message) { - return new ErrorMessage(throwable, headers, (Message) inputMessage); - } - else { - return new ErrorMessage(throwable, headers); - } - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdvice.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdvice.java deleted file mode 100644 index aeb701e1354..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdvice.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.lang.reflect.UndeclaredThrowableException; -import java.time.Duration; - -import com.rabbitmq.client.ConfirmCallback; -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.rabbit.core.RabbitOperations; -import org.springframework.integration.handler.advice.HandleMessageAdvice; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * A {@link HandleMessageAdvice} that causes all downstream {@link RabbitOperations} operations to be executed - * on the same channel, as long as there are no thread handoffs, since the channel is - * bound to the thread. The same RabbitOperations must be used in this and all downstream - * components. Typically, used with a splitter or some other mechanism that would cause - * multiple messages to be sent. Optionally waits for publisher confirms if the channel is - * so configured. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.1 - * - */ -public class BoundRabbitChannelAdvice implements HandleMessageAdvice { - - private final Log logger = LogFactory.getLog(getClass()); - - private final RabbitOperations operations; - - private final @Nullable Duration waitForConfirmsTimeout; - - private final ConfirmCallback ackCallback = this::handleAcks; - - private final ConfirmCallback nackCallback = this::handleNacks; - - /** - * Construct an instance that doesn't wait for confirms. - * @param operations the operations. - */ - public BoundRabbitChannelAdvice(RabbitOperations operations) { - this(operations, null); - } - - /** - * Construct an instance that waits for publisher confirms (if - * configured and waitForConfirmsTimeout is not null). - * @param operations the operations. - * @param waitForConfirmsTimeout the timeout. - */ - public BoundRabbitChannelAdvice(RabbitOperations operations, @Nullable Duration waitForConfirmsTimeout) { - Assert.notNull(operations, "'operations' cannot be null"); - this.operations = operations; - this.waitForConfirmsTimeout = waitForConfirmsTimeout; - if (this.waitForConfirmsTimeout != null) { - Assert.isTrue(operations.getConnectionFactory().isSimplePublisherConfirms(), - "'waitForConfirmsTimeout' requires a connection factory with simple publisher confirms enabled"); - } - } - - @Override - public @Nullable Object invoke(MethodInvocation invocation) throws Throwable { - try { - return this.operations.invoke(operations -> { - try { - Object result = invocation.proceed(); - if (this.waitForConfirmsTimeout != null) { - this.operations.waitForConfirmsOrDie(this.waitForConfirmsTimeout.toMillis()); - } - return result; - } - catch (Throwable t) { // NOSONAR - rethrown below - ReflectionUtils.rethrowRuntimeException(t); - return null; // not reachable - satisfy compiler - } - }, this.ackCallback, this.nackCallback); - } - catch (UndeclaredThrowableException ute) { - throw ute.getCause(); - } - } - - private void handleAcks(long deliveryTag, boolean multiple) { - doHandleAcks(deliveryTag, multiple, true); - } - - private void handleNacks(long deliveryTag, boolean multiple) { - doHandleAcks(deliveryTag, multiple, false); - } - - private void doHandleAcks(long deliveryTag, boolean multiple, boolean ack) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Publisher confirm " + (!ack ? "n" : "") + "ack: " + deliveryTag + ", " + - "multiple: " + multiple); - } - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java deleted file mode 100644 index 0630cf38526..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.JavaUtils; -import org.springframework.integration.mapping.AbstractHeaderMapper; -import org.springframework.integration.mapping.support.JsonHeaders; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.MimeType; -import org.springframework.util.StringUtils; - -/** - * Default implementation of {@link AmqpHeaderMapper}. - *

- * By default, this implementation will only copy AMQP properties (e.g. contentType) to and from - * Spring Integration MessageHeaders. Any user-defined headers within the AMQP - * MessageProperties will NOT be copied to or from an AMQP Message unless - * explicitly identified via 'requestHeaderNames' and/or 'replyHeaderNames' - * (see {@link AbstractHeaderMapper#setRequestHeaderNames(String[])} - * and {@link AbstractHeaderMapper#setReplyHeaderNames(String[])}} - * as well as 'mapped-request-headers' and 'mapped-reply-headers' attributes of the AMQP adapters). - * If you need to copy all user-defined headers simply use wild-card character '*'. - *

- * Constants for the AMQP header keys are defined in {@link AmqpHeaders}. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Stephane Nicoll - * @author Steve Singer - * @author Glenn Renfro - * - * @since 2.1 - */ -public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper implements AmqpHeaderMapper { - - private static final List STANDARD_HEADER_NAMES = new ArrayList<>(); - - static { - STANDARD_HEADER_NAMES.add(AmqpHeaders.APP_ID); - STANDARD_HEADER_NAMES.add(AmqpHeaders.CLUSTER_ID); - STANDARD_HEADER_NAMES.add(AmqpHeaders.CONTENT_ENCODING); - STANDARD_HEADER_NAMES.add(AmqpHeaders.CONTENT_LENGTH); - STANDARD_HEADER_NAMES.add(AmqpHeaders.CONTENT_TYPE); - STANDARD_HEADER_NAMES.add(AmqpHeaders.CORRELATION_ID); - STANDARD_HEADER_NAMES.add(AmqpHeaders.DELAY); - STANDARD_HEADER_NAMES.add(AmqpHeaders.DELIVERY_MODE); - STANDARD_HEADER_NAMES.add(AmqpHeaders.DELIVERY_TAG); - STANDARD_HEADER_NAMES.add(AmqpHeaders.EXPIRATION); - STANDARD_HEADER_NAMES.add(AmqpHeaders.MESSAGE_COUNT); - STANDARD_HEADER_NAMES.add(AmqpHeaders.MESSAGE_ID); - STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_DELAY); - STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_DELIVERY_MODE); - STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_EXCHANGE); - STANDARD_HEADER_NAMES.add(AmqpHeaders.RECEIVED_ROUTING_KEY); - STANDARD_HEADER_NAMES.add(AmqpHeaders.REDELIVERED); - STANDARD_HEADER_NAMES.add(AmqpHeaders.REPLY_TO); - STANDARD_HEADER_NAMES.add(AmqpHeaders.TIMESTAMP); - STANDARD_HEADER_NAMES.add(AmqpHeaders.TYPE); - STANDARD_HEADER_NAMES.add(AmqpHeaders.USER_ID); - STANDARD_HEADER_NAMES.add(JsonHeaders.TYPE_ID); - STANDARD_HEADER_NAMES.add(JsonHeaders.CONTENT_TYPE_ID); - STANDARD_HEADER_NAMES.add(JsonHeaders.KEY_TYPE_ID); - STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_CORRELATION); - STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_TO_STACK); - } - - @SuppressWarnings("this-escape") - protected DefaultAmqpHeaderMapper(String @Nullable [] requestHeaderNames, String @Nullable [] replyHeaderNames) { - super(AmqpHeaders.PREFIX, STANDARD_HEADER_NAMES, STANDARD_HEADER_NAMES); - if (requestHeaderNames != null) { - setRequestHeaderNames(requestHeaderNames); - } - if (replyHeaderNames != null) { - setReplyHeaderNames(replyHeaderNames); - } - } - - /** - * Extract "standard" headers from an AMQP MessageProperties instance. - */ - @Override - protected Map extractStandardHeaders(MessageProperties amqpMessageProperties) { - Map headers = new HashMap<>(); - try { - JavaUtils.INSTANCE - .acceptIfNotNull(AmqpHeaders.APP_ID, amqpMessageProperties.getAppId(), headers::put) - .acceptIfNotNull(AmqpHeaders.CLUSTER_ID, amqpMessageProperties.getClusterId(), headers::put) - .acceptIfNotNull(AmqpHeaders.CONTENT_ENCODING, amqpMessageProperties.getContentEncoding(), - headers::put); - long contentLength = amqpMessageProperties.getContentLength(); - JavaUtils.INSTANCE - .acceptIfCondition(contentLength > 0, AmqpHeaders.CONTENT_LENGTH, contentLength, headers::put) - .acceptIfHasText(AmqpHeaders.CONTENT_TYPE, amqpMessageProperties.getContentType(), headers::put) - .acceptIfHasText(AmqpHeaders.CORRELATION_ID, amqpMessageProperties.getCorrelationId(), headers::put) - .acceptIfNotNull(AmqpHeaders.RECEIVED_DELIVERY_MODE, - amqpMessageProperties.getReceivedDeliveryMode(), headers::put); - long deliveryTag = amqpMessageProperties.getDeliveryTag(); - JavaUtils.INSTANCE - .acceptIfCondition(deliveryTag > 0, AmqpHeaders.DELIVERY_TAG, deliveryTag, headers::put) - .acceptIfHasText(AmqpHeaders.EXPIRATION, amqpMessageProperties.getExpiration(), headers::put); - Integer messageCount = amqpMessageProperties.getMessageCount(); - JavaUtils.INSTANCE - .acceptIfCondition(messageCount != null && messageCount > 0, AmqpHeaders.MESSAGE_COUNT, - messageCount, headers::put) - .acceptIfHasText(AmqpHeaders.MESSAGE_ID, amqpMessageProperties.getMessageId(), headers::put); - Integer priority = amqpMessageProperties.getPriority(); - JavaUtils.INSTANCE - .acceptIfCondition(priority > 0, IntegrationMessageHeaderAccessor.PRIORITY, priority, headers::put) - .acceptIfNotNull(AmqpHeaders.RECEIVED_DELAY, amqpMessageProperties.getReceivedDelayLong(), - headers::put) - .acceptIfNotNull(AmqpHeaders.RECEIVED_EXCHANGE, amqpMessageProperties.getReceivedExchange(), - headers::put) - .acceptIfHasText(AmqpHeaders.RECEIVED_ROUTING_KEY, amqpMessageProperties.getReceivedRoutingKey(), - headers::put) - .acceptIfNotNull(AmqpHeaders.REDELIVERED, amqpMessageProperties.isRedelivered(), headers::put) - .acceptIfNotNull(AmqpHeaders.REPLY_TO, amqpMessageProperties.getReplyTo(), headers::put) - .acceptIfNotNull(AmqpHeaders.TIMESTAMP, amqpMessageProperties.getTimestamp(), headers::put) - .acceptIfHasText(AmqpHeaders.TYPE, amqpMessageProperties.getType(), headers::put) - .acceptIfHasText(AmqpHeaders.RECEIVED_USER_ID, - amqpMessageProperties.getReceivedUserId(), headers::put); - - headers.put(AmqpHeaders.RETRY_COUNT, amqpMessageProperties.getRetryCount()); - - for (String jsonHeader : JsonHeaders.HEADERS) { - Object value = amqpMessageProperties.getHeaders().get(jsonHeader.replaceFirst(JsonHeaders.PREFIX, "")); - if (value instanceof String && StringUtils.hasText((String) value)) { - headers.put(jsonHeader, value); - } - } - } - catch (Exception e) { - this.logger.warn("error occurred while mapping from AMQP properties to MessageHeaders", e); - } - return headers; - } - - /** - * Extract user-defined headers from an AMQP MessageProperties instance. - */ - @Override - protected Map extractUserDefinedHeaders(MessageProperties amqpMessageProperties) { - return amqpMessageProperties.getHeaders(); - } - - /** - * Maps headers from a Spring Integration MessageHeaders instance to the MessageProperties - * of an AMQP Message. - */ - @Override - protected void populateStandardHeaders(Map headers, MessageProperties amqpMessageProperties) { - populateStandardHeaders(null, headers, amqpMessageProperties); - } - - /** - * Maps headers from a Spring Integration MessageHeaders instance to the MessageProperties - * of an AMQP Message. - */ - @Override - protected void populateStandardHeaders(@Nullable Map allHeaders, Map headers, - MessageProperties amqpMessageProperties) { - - JavaUtils.INSTANCE - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.APP_ID, String.class), - amqpMessageProperties::setAppId) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.CLUSTER_ID, String.class), - amqpMessageProperties::setClusterId) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.CONTENT_ENCODING, String.class), - amqpMessageProperties::setContentEncoding) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.CONTENT_LENGTH, Long.class), - amqpMessageProperties::setContentLength) - .acceptIfHasText(extractContentTypeAsString(headers), - amqpMessageProperties::setContentType) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.CORRELATION_ID, String.class), - amqpMessageProperties::setCorrelationId) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.DELAY, Long.class), - amqpMessageProperties::setDelayLong) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.class), - amqpMessageProperties::setDeliveryMode) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.DELIVERY_TAG, Long.class), - amqpMessageProperties::setDeliveryTag) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.EXPIRATION, String.class), - amqpMessageProperties::setExpiration) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.RETRY_COUNT, Long.class), - amqpMessageProperties::setRetryCount) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.MESSAGE_COUNT, Integer.class), - amqpMessageProperties::setMessageCount); - String messageId = getHeaderIfAvailable(headers, AmqpHeaders.MESSAGE_ID, String.class); - if (StringUtils.hasText(messageId)) { - amqpMessageProperties.setMessageId(messageId); - } - else if (allHeaders != null) { - UUID id = getHeaderIfAvailable(allHeaders, MessageHeaders.ID, UUID.class); - if (id != null) { - amqpMessageProperties.setMessageId(id.toString()); - } - } - JavaUtils.INSTANCE - .acceptIfNotNull(getHeaderIfAvailable(headers, IntegrationMessageHeaderAccessor.PRIORITY, - Integer.class), - amqpMessageProperties::setPriority) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.RECEIVED_EXCHANGE, String.class), - amqpMessageProperties::setReceivedExchange) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.RECEIVED_ROUTING_KEY, String.class), - amqpMessageProperties::setReceivedRoutingKey) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.REDELIVERED, Boolean.class), - amqpMessageProperties::setRedelivered) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.REPLY_TO, String.class), - amqpMessageProperties::setReplyTo); - Date timestamp = getHeaderIfAvailable(headers, AmqpHeaders.TIMESTAMP, Date.class); - if (timestamp != null) { - amqpMessageProperties.setTimestamp(timestamp); - } - else if (allHeaders != null) { - Long ts = getHeaderIfAvailable(allHeaders, MessageHeaders.TIMESTAMP, Long.class); - if (ts != null) { - amqpMessageProperties.setTimestamp(new Date(ts)); - } - } - JavaUtils.INSTANCE - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.TYPE, String.class), - amqpMessageProperties::setType) - .acceptIfNotNull(getHeaderIfAvailable(headers, AmqpHeaders.USER_ID, String.class), - amqpMessageProperties::setUserId); - - mapJsonHeaders(headers, amqpMessageProperties); - - JavaUtils.INSTANCE - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.SPRING_REPLY_CORRELATION, String.class), - replyCorrelation -> amqpMessageProperties - .setHeader("spring_reply_correlation", replyCorrelation)) - .acceptIfHasText(getHeaderIfAvailable(headers, AmqpHeaders.SPRING_REPLY_TO_STACK, String.class), - replyToStack -> amqpMessageProperties.setHeader("spring_reply_to", replyToStack)); - } - - private void mapJsonHeaders(Map headers, MessageProperties amqpMessageProperties) { - /* - * If the MessageProperties already contains JsonHeaders, don't overwrite them here because they were - * set up by a message converter. - */ - if (!amqpMessageProperties.getHeaders().containsKey(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))) { - Map jsonHeaders = new HashMap<>(); - - for (String jsonHeader : JsonHeaders.HEADERS) { - if (!JsonHeaders.RESOLVABLE_TYPE.equals(jsonHeader)) { - Object value = getHeaderIfAvailable(headers, jsonHeader, Object.class); - if (value != null) { - headers.remove(jsonHeader); - if (value instanceof Class) { - value = ((Class) value).getName(); - } - jsonHeaders.put(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""), value.toString()); - } - } - } - amqpMessageProperties.getHeaders().putAll(jsonHeaders); - } - } - - @Override - protected void populateUserDefinedHeader(String headerName, Object headerValue, - MessageProperties amqpMessageProperties) { - // do not overwrite an existing header with the same key - // TODO: do we need to expose a boolean 'overwrite' flag? - if (!amqpMessageProperties.getHeaders().containsKey(headerName) && - !AmqpHeaders.CONTENT_TYPE.equals(headerName) && - !headerName.startsWith(JsonHeaders.PREFIX)) { - amqpMessageProperties.setHeader(headerName, headerValue); - } - } - - /** - * Will extract Content-Type from MessageHeaders and convert it to String if possible - * Required since Content-Type can be represented as org.springframework.util.MimeType. - * - */ - private @Nullable String extractContentTypeAsString(Map headers) { - String contentTypeStringValue = null; - - Object contentType = getHeaderIfAvailable(headers, AmqpHeaders.CONTENT_TYPE, Object.class); - - if (contentType != null) { - String contentTypeClassName = contentType.getClass().getName(); - - if (contentType instanceof MimeType) { - contentTypeStringValue = contentType.toString(); - } - else if (contentType instanceof String) { - contentTypeStringValue = (String) contentType; - } - else { - if (logger.isWarnEnabled()) { - logger.warn("skipping header '" + AmqpHeaders.CONTENT_TYPE + - "' since it is not of expected type [" + contentTypeClassName + "]"); - } - } - } - return contentTypeStringValue; - } - - @Override - public Map toHeadersFromRequest(MessageProperties source) { - Map headersFromRequest = super.toHeadersFromRequest(source); - addConsumerMetadata(source, headersFromRequest); - return headersFromRequest; - } - - private void addConsumerMetadata(MessageProperties messageProperties, Map headers) { - String consumerTag = messageProperties.getConsumerTag(); - if (consumerTag != null) { - headers.put(AmqpHeaders.CONSUMER_TAG, consumerTag); - } - String consumerQueue = messageProperties.getConsumerQueue(); - if (consumerQueue != null) { - headers.put(AmqpHeaders.CONSUMER_QUEUE, consumerQueue); - } - } - - /** - * Construct a default inbound header mapper. - * @return the mapper. - * @since 4.3 - * @see #inboundRequestHeaders() - * @see #inboundReplyHeaders() - */ - public static DefaultAmqpHeaderMapper inboundMapper() { - return new DefaultAmqpHeaderMapper(inboundRequestHeaders(), inboundReplyHeaders()); - } - - /** - * Construct a default outbound header mapper. - * @return the mapper. - * @since 4.3 - * @see #outboundRequestHeaders() - * @see #outboundReplyHeaders() - */ - public static DefaultAmqpHeaderMapper outboundMapper() { - return new DefaultAmqpHeaderMapper(outboundRequestHeaders(), outboundReplyHeaders()); - } - - /** - * @return the default request headers for an inbound mapper. - * @since 4.3 - */ - public static String[] inboundRequestHeaders() { - return new String[] {"*"}; - } - - /** - * @return the default reply headers for an inbound mapper. - * @since 4.3 - */ - public static String[] inboundReplyHeaders() { - return safeOutboundHeaders(); - } - - /** - * @return the default request headers for an outbound mapper. - * @since 4.3 - */ - public static String[] outboundRequestHeaders() { - return safeOutboundHeaders(); - } - - /** - * @return the default reply headers for an outbound mapper. - * @since 4.3 - */ - public static String[] outboundReplyHeaders() { - return new String[] {"*"}; - } - - private static String[] safeOutboundHeaders() { - return new String[] {"!x-*", "*"}; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/EndpointUtils.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/EndpointUtils.java deleted file mode 100644 index 24ed5e5f238..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/EndpointUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.util.List; -import java.util.Objects; - -import com.rabbitmq.client.Channel; -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; - -/** - * Utility methods for messaging endpoints. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.1.3 - * - */ -public final class EndpointUtils { - - private static final String LEFE_MESSAGE = "Message conversion failed"; - - private EndpointUtils() { - } - - /** - * Return an {@link ListenerExecutionFailedException} or a {@link ManualAckListenerExecutionFailedException} - * depending on whether isManualAck is false or true. - * @param message the failed message. - * @param channel the channel. - * @param isManualAck true if the container uses manual acknowledgment. - * @param ex the exception. - * @return the exception. - */ - public static ListenerExecutionFailedException errorMessagePayload(Message message, - @Nullable Channel channel, boolean isManualAck, Exception ex) { - - return isManualAck - ? new ManualAckListenerExecutionFailedException(LEFE_MESSAGE, ex, Objects.requireNonNull(channel), - message.getMessageProperties().getDeliveryTag(), message) - : new ListenerExecutionFailedException(LEFE_MESSAGE, ex, message); - } - - /** - * Return an {@link ListenerExecutionFailedException} or a {@link ManualAckListenerExecutionFailedException} - * depending on whether isManualAck is false or true. - * @param messages the failed messages. - * @param channel the channel. - * @param isManualAck true if the container uses manual acknowledgment. - * @param ex the exception. - * @return the exception. - * @since 5.3 - */ - public static ListenerExecutionFailedException errorMessagePayload(List messages, - @Nullable Channel channel, boolean isManualAck, Exception ex) { - - return isManualAck - ? new ManualAckListenerExecutionFailedException(LEFE_MESSAGE, ex, Objects.requireNonNull(channel), - messages.get(messages.size() - 1).getMessageProperties().getDeliveryTag(), - messages.toArray(new Message[0])) - : new ListenerExecutionFailedException(LEFE_MESSAGE, ex, messages.toArray(new Message[0])); - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/ManualAckListenerExecutionFailedException.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/ManualAckListenerExecutionFailedException.java deleted file mode 100644 index ca517c2c0b2..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/ManualAckListenerExecutionFailedException.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.io.Serial; - -import com.rabbitmq.client.Channel; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; - -/** - * A {@link ListenerExecutionFailedException} enhanced with the channel and delivery tag. - * Used for conversion errors when using manual acks. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.1.3 - * - */ -public class ManualAckListenerExecutionFailedException extends ListenerExecutionFailedException { - - @Serial - private static final long serialVersionUID = 1L; - - private final transient Channel channel; - - private final long deliveryTag; - - /** - * Construct an instance with the provided properties. - * @param msg the exception message. - * @param cause the cause. - * @param channel the channel. - * @param deliveryTag the delivery tag for the last message. - * @param failedMessages the failed message(s). - * @since 5.3 - */ - public ManualAckListenerExecutionFailedException(String msg, Throwable cause, - Channel channel, long deliveryTag, Message... failedMessages) { - - super(msg, cause, failedMessages); - this.channel = channel; - this.deliveryTag = deliveryTag; - } - - /** - * Return the channel. - * @return the channel. - */ - public Channel getChannel() { - return this.channel; - } - - /** - * Return the delivery tag for the last failed message. - * @return the tag. - */ - public long getDeliveryTag() { - return this.deliveryTag; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/MappingUtils.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/MappingUtils.java deleted file mode 100644 index 5f062d5fe6c..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/MappingUtils.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import org.jspecify.annotations.Nullable; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.MimeType; - -/** - * Utility methods used during message mapping. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -public final class MappingUtils { - - private MappingUtils() { - } - - /** - * Map an o.s.m.Message to an o.s.a.core.Message. When using a - * {@link ContentTypeDelegatingMessageConverter}, {@link AmqpHeaders#CONTENT_TYPE} and - * {@link MessageHeaders#CONTENT_TYPE} will be used for the selection, with the AMQP - * header taking precedence. - * @param requestMessage the request message. - * @param converter the message converter to use. - * @param headerMapper the header mapper to use. - * @param defaultDeliveryMode the default delivery mode. - * @param headersMappedLast true if headers are mapped after conversion. - * @return the mapped Message. - */ - public static org.springframework.amqp.core.Message mapMessage(Message requestMessage, - MessageConverter converter, AmqpHeaderMapper headerMapper, @Nullable MessageDeliveryMode defaultDeliveryMode, - boolean headersMappedLast) { - - return doMapMessage(requestMessage, converter, headerMapper, defaultDeliveryMode, headersMappedLast, false); - } - - /** - * Map a reply o.s.m.Message to an o.s.a.core.Message. When using a - * {@link ContentTypeDelegatingMessageConverter}, {@link AmqpHeaders#CONTENT_TYPE} and - * {@link MessageHeaders#CONTENT_TYPE} will be used for the selection, with the AMQP - * header taking precedence. - * @param replyMessage the reply message. - * @param converter the message converter to use. - * @param headerMapper the header mapper to use. - * @param defaultDeliveryMode the default delivery mode. - * @param headersMappedLast true if headers are mapped after conversion. - * @return the mapped Message. - * @since 5.1.9 - */ - public static org.springframework.amqp.core.Message mapReplyMessage(Message replyMessage, - MessageConverter converter, AmqpHeaderMapper headerMapper, - @Nullable MessageDeliveryMode defaultDeliveryMode, boolean headersMappedLast) { - - return doMapMessage(replyMessage, converter, headerMapper, defaultDeliveryMode, headersMappedLast, true); - } - - private static org.springframework.amqp.core.Message doMapMessage(Message message, - MessageConverter converter, AmqpHeaderMapper headerMapper, - @Nullable MessageDeliveryMode defaultDeliveryMode, boolean headersMappedLast, boolean reply) { - - MessageProperties amqpMessageProperties = new MessageProperties(); - org.springframework.amqp.core.Message amqpMessage = mapMessage(message, converter, headerMapper, - headersMappedLast, reply, amqpMessageProperties); - checkDeliveryMode(message, amqpMessageProperties, defaultDeliveryMode); - return amqpMessage; - } - - /** - * Map a reply o.s.m.Message to an o.s.a.core.Message. When using a - * {@link ContentTypeDelegatingMessageConverter}, {@link AmqpHeaders#CONTENT_TYPE} and - * {@link MessageHeaders#CONTENT_TYPE} will be used for the selection, with the AMQP - * header taking precedence. - * @param message the reply message. - * @param converter the message converter to use. - * @param headerMapper the header mapper to use. - * @param headersMappedLast true if headers are mapped after conversion. - * @return the mapped Message. - * @since 6.0 - */ - public static org.springframework.amqp.core.Message mapMessage(Message message, MessageConverter converter, - AmqpHeaderMapper headerMapper, boolean headersMappedLast, boolean reply, - MessageProperties amqpMessageProperties) { - - org.springframework.amqp.core.Message amqpMessage; - if (!headersMappedLast) { - mapHeaders(message.getHeaders(), amqpMessageProperties, headerMapper, reply); - } - if (converter instanceof ContentTypeDelegatingMessageConverter && headersMappedLast) { - String contentType = contentTypeAsString(message.getHeaders()); - if (contentType != null) { - amqpMessageProperties.setContentType(contentType); - } - } - amqpMessage = converter.toMessage(message.getPayload(), amqpMessageProperties); - if (headersMappedLast) { - mapHeaders(message.getHeaders(), amqpMessageProperties, headerMapper, reply); - } - return amqpMessage; - } - - private static void mapHeaders(MessageHeaders messageHeaders, MessageProperties amqpMessageProperties, - AmqpHeaderMapper headerMapper, boolean reply) { - - if (reply) { - headerMapper.fromHeadersToReply(messageHeaders, amqpMessageProperties); - } - else { - headerMapper.fromHeadersToRequest(messageHeaders, amqpMessageProperties); - } - } - - private static @Nullable String contentTypeAsString(MessageHeaders headers) { - Object contentType = headers.get(AmqpHeaders.CONTENT_TYPE); - if (contentType instanceof MimeType) { - contentType = contentType.toString(); - } - if (contentType instanceof String) { - return (String) contentType; - } - else if (contentType != null) { - throw new IllegalArgumentException(AmqpHeaders.CONTENT_TYPE - + " header must be a MimeType or String, found: " + contentType.getClass().getName()); - } - return null; - } - - /** - * Check the delivery mode and update with the default if not already present. - * @param requestMessage the request message. - * @param messageProperties the mapped message properties. - * @param defaultDeliveryMode the default delivery mode. - */ - public static void checkDeliveryMode(Message requestMessage, MessageProperties messageProperties, - @Nullable MessageDeliveryMode defaultDeliveryMode) { - if (defaultDeliveryMode != null && - requestMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE) == null) { - messageProperties.setDeliveryMode(defaultDeliveryMode); - } - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/NackedAmqpMessageException.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/NackedAmqpMessageException.java deleted file mode 100644 index a5212dc4a29..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/NackedAmqpMessageException.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.io.Serial; - -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; - -/** - * An exception representing a negatively acknowledged message from a - * publisher confirm. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3.12 - * - */ -public class NackedAmqpMessageException extends MessagingException { - - @Serial - private static final long serialVersionUID = 1L; - - private final String nackReason; - - private final transient Object correlationData; - - public NackedAmqpMessageException(Message message, Object correlationData, String nackReason) { - super(message); - this.correlationData = correlationData; - this.nackReason = nackReason; - } - - public Object getCorrelationData() { - return this.correlationData; - } - - public String getNackReason() { - return this.nackReason; - } - - @Override - public String toString() { - return super.toString() + " [correlationData=" + this.correlationData + ", nackReason=" + this.nackReason - + "]"; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/ReturnedAmqpMessageException.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/ReturnedAmqpMessageException.java deleted file mode 100644 index 507c0717c25..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/ReturnedAmqpMessageException.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.io.Serial; - -import org.springframework.amqp.core.Message; -import org.springframework.messaging.MessagingException; - -/** - * A MessagingException for a returned message. - * - * @author Gary Russell - * @since 4.3.12 - * - */ -public class ReturnedAmqpMessageException extends MessagingException { - - @Serial - private static final long serialVersionUID = 1L; - - private final Message amqpMessage; - - private final int replyCode; - - private final String replyText; - - private final String exchange; - - private final String routingKey; - - public ReturnedAmqpMessageException(org.springframework.messaging.Message message, Message amqpMessage, - int replyCode, String replyText, String exchange, String routingKey) { - super(message); - this.amqpMessage = amqpMessage; - this.replyCode = replyCode; - this.replyText = replyText; - this.exchange = exchange; - this.routingKey = routingKey; - } - - public Message getAmqpMessage() { - return this.amqpMessage; - } - - public int getReplyCode() { - return this.replyCode; - } - - public String getReplyText() { - return this.replyText; - } - - public String getExchange() { - return this.exchange; - } - - public String getRoutingKey() { - return this.routingKey; - } - - @Override - public String toString() { - return super.toString() + " [amqpMessage=" + this.amqpMessage + ", replyCode=" + this.replyCode - + ", replyText=" + this.replyText + ", exchange=" + this.exchange + ", routingKey=" + this.routingKey - + "]"; - } - -} diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/package-info.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/package-info.java deleted file mode 100644 index 65ba8774939..00000000000 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides AMQP support classes. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.amqp.support; diff --git a/spring-integration-amqp/src/main/resources/META-INF/spring.handlers b/spring-integration-amqp/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index b0847f108a2..00000000000 --- a/spring-integration-amqp/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/amqp=org.springframework.integration.amqp.config.AmqpNamespaceHandler diff --git a/spring-integration-amqp/src/main/resources/META-INF/spring.schemas b/spring-integration-amqp/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index 2165c778bed..00000000000 --- a/spring-integration-amqp/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,22 +0,0 @@ -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-2.1.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-2.2.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-3.0.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.0.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.1.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.2.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.3.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-5.0.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-5.1.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-5.2.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -http\://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-2.1.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-2.2.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-3.0.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.0.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.1.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.2.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-4.3.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-5.0.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-5.1.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp-5.2.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd -https\://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd=org/springframework/integration/amqp/config/spring-integration-amqp.xsd diff --git a/spring-integration-amqp/src/main/resources/META-INF/spring.tooling b/spring-integration-amqp/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index 0d3f0b185d1..00000000000 --- a/spring-integration-amqp/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration AMQP namespace -http\://www.springframework.org/schema/integration/amqp@name=integration AMQP Namespace -http\://www.springframework.org/schema/integration/amqp@prefix=int-amqp -http\://www.springframework.org/schema/integration/amqp@icon=org/springframework/integration/amqp/config/spring-integration-amqp.gif diff --git a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.gif b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.gif deleted file mode 100644 index cdccbae245a..00000000000 Binary files a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.gif and /dev/null differ diff --git a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd deleted file mode 100644 index 14163860e77..00000000000 --- a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd +++ /dev/null @@ -1,1179 +0,0 @@ - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint' - with the 'expectReply = false' that will publish an AMQP Message to the provided Exchange. - - - - - - - - - Unique ID for this adapter. - - - - - - - Message Channel to which Messages should be sent in order to have them converted and - published to an AMQP Exchange. - If this attribute is not provided, the ID will be used to create a new DirectChannel, - and then instead of using that - ID as the bean name of the EventDrivenConsumer instance that hosts the MessageHandler - responsible for publishing the - AMQP Messages, that EventDrivenConsumer's bean name will be the ID plus the added - suffix: ".adapter" - - - - - - - - - - - - - An AmqpTemplate; if not supplied, a bean name 'amqpTemplate' is - expected. - - - - - - - - - - - - Set to true if you want to block the calling thread until a publisher confirm has - been received. Requires a template configured for returns. If a confirm is not - received within the confirm timeout or a negative acknowledgment or returned - message is received, an exception will be thrown. - - - - - - - - - - - - Set to true to send payloads of type Iterable<Message<?>> - as discrete messages within a single template invocation and optionally - wait for the confirms. - - - - - - - - - - - - - - - - Configures a Message Producing Endpoint for the - 'org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter' - that will receive AMQP Messages sent to a given queue and then forward those messages to a Message - Channel. - - - - - - - - - Message Channel to which converted Messages should be sent. If this attribute is not - provided, the ID will - be used to create a new DirectChannel, and then instead of using that ID as the bean - name of the Channel Adapter - instance, the bean name will be the ID plus the added suffix: ".adapter" - - - - - - - - - - - - - When the listener container's 'consumerBatchEnabled' property is true, - this attribute determines the payload type. 'MESSAGES' (default) means - the payload will be a list of messages; 'EXTRACT_PAYLOADS' means the payload - will be a list of converted payloads. - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint' - that will publish an AMQP Message to the provided Exchange and expect a reply Message. - The thread blocks waiting for a reply or timeout; uses 'RabbitTemplate.sendAndReceive()'. - - - - - - - - - - An AmqpTemplate; if not supplied, a bean name 'amqpTemplate' is - expected. - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.amqp.outbound.AsyncAmqpOutboundGateway' - that will publish an AMQP Message to the provided Exchange and expect a reply Message. - The sending thread returns immediately; the reply is sent asynchronously; uses - 'AsyncRabbitTemplate.sendAndReceive()'. - - - - - - - - - An AsyncRabbitTemplate; if not supplied, a bean name 'asyncRabbitTemplate' is - expected. - - - - - - - - - - - - - - - - - Configures a Messaging Gateway Endpoint for the - 'org.springframework.integration.amqp.inbound.AmqpInboundGateway' that will receive AMQP Messages - sent to a given queue and then forward those messages to a Message Channel. - If a reply Message is returned, it will also send that to the 'replyTo' provide by the AMQP request - Message. - - - - - - - - - Message Channel to which converted Messages should be sent. - - - - - - - - - - - - Message Channel where reply Messages will be expected. - - - - - - - - - - - - - - - - - - - - - - - - - - - The AmqpTemplate bean reference to be used for sending replies. - Defaults to `RabbitTemplate` based on the provided `ConnectionFactory`. - - - - - - - The 'defaultReplyTo' address with the form '(exchange)/(routingKey)' - (or '(queueName)' - in which case the default exchange will be used - with the queue name as the routing key) - if the request message doesn't have 'replyTo' property. - If this property isn't specified too, the gateway relies on - the `AmqpTemplate` configuration. - - - - - - - Whether reply headers are mapped before or after conversion from a messaging Message to - a spring amqp Message. Set to true, for example, if you wish to override the - contentType header set by the converter. - - - - - - - - - - - - - - - Creates a point-to-point channel that is backed by an AMQP Queue. - - - - - - - - - Indicate whether this channel should be message-driven (subscribable) or not (pollable). - - - - - - - Provide an explicitly configured queue name. If this is not provided, then a Queue will - be created - implicitly with the same name as the channel itself (the "id" of this element). If this - channel is - not message-driven, the implicit creation will require that either an AmqpAdmin instance - has been - provided via the "amqp-admin" attribute or that the configured AmqpTemplate is an - instance of RabbitTemplate. - If the channel is message-driven, the AmqpAdmin will be created using the underlying - listener container's - ConnectionFactory. - - - - - - - An AmqpAdmin instance to use when declaring a Queue implicitly. This is only needed if - an explicit - "queue-name" is not provided and the channel is not message-driven. Even then, if the - referenced - AmqpTemplate is an instance of RabbitTemplate, the AmqpAdmin can be constructed from - that template's - ConnectionFactory. - - - - - - - - - - - - - - - - - Creates a publish-subscribe-channel that is backed by an AMQP FanoutExchange. - Always message-driven (subscribable). - - - - - - - - - Reference to a FanoutExchange instance to which this channel should send Messages. If - not provided, - a FanoutExchange will be declared with this channel's name prefixed by "si.fanout.". - A Queue will be declared automatically and bound to that exchange to handle the consumer - role - of this channel. - - - - - - - - - - - - - - - - - Base type for 'channel' and 'publish-subscribe-channel'. - - - - - - - - - - - - - Unique ID for this Message Channel. - - - - - - - The default delivery mode for messages; 'PERSISTENT' or 'NON_PERSISTENT'. Overridden if the - 'header-mapper' - sets the delivery mode. The 'DefaultHeaderMapper' sets the value if the - Spring Integration message header 'amqp_deliveryMode' is present. If this attribute is not supplied - and - the header mapper doesn't set it, the default depends on the underlying spring-amqp - 'MessagePropertiesConverter' - used by the 'RabbitTemplate'. If that is not customized at all, the default is 'PERSISTENT'. - - - - - - - - - - Set to 'true' to extract the message payload and map the o.s.messaging.Message to an - o.s.amqp.core.Message in - a similar manner to a pair of channel adapters. When 'false' the entire message is converted - requiring either - Java serializable contents or a custom message converter. Also see inbound and outbound mapped - headers. - Also see 'headers-last', which only applies if this is 'true'. - - - - - - - The header mapper to use when sending and 'extract-payload' is true. The default mapper is - DefaultAmqpHeaderMapper.outboundMapper() which maps all headers except 'x-*'. - - - - - - - The header mapper to use when receiving and 'extract-payload' is true. The default mapper is - DefaultAmqpHeaderMapper.inboundMapper() which maps all headers. - - - - - - - - - - - - Base type for the 'outbound-channel-adapter' and 'outbound-gateway' elements. - - - - - - - - - - The fixed name of the AMQP Exchange to which Messages should be sent. If not provided, Messages will - be sent to the default, no-name Exchange. - - - - - - - The exchange name to use when sending Messages evaluated as an expression on the message (e.g. - 'headers.exchange'). By default, this will be an emtpy String. - - - - - - - The fixed routing-key to use when sending Messages. By default, this will be an empty String. - - - - - - - The routing-key to use when sending Messages evaluated as an expression on the message (e.g. - 'payload.key'). By default, this will be an empty String. - - - - - - - The order for this consumer when multiple consumers are registered thereby enabling load-balancing - and/or failover. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The default delivery mode for messages; 'PERSISTENT' or 'NON_PERSISTENT'. Overridden if the - 'header-mapper' - sets the delivery mode. The 'DefaultHeaderMapper' sets the value if the - Spring Integration message header 'amqp_deliveryMode' is present. If this attribute is not supplied - and - the header mapper doesn't set it, the default depends on the underlying spring-amqp - 'MessagePropertiesConverter' - used by the 'RabbitTemplate'. If that is not customized at all, the default is 'PERSISTENT'. - - - - - - - - - - By default, the connection is established lazily, when the first message is sent. If you wish to - detect - connection configuration problems during application initialization, set this to 'false'. - If the eager connection fails, an ERROR log will be emitted. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Unique ID for this gateway. - - - - - - - Message Channel to which Messages should be sent in order to have them converted and - published to an AMQP Exchange. - - - - - - - - - - - - Specify whether this outbound gateway must return a non-null value. This value is - 'true' by default, and a ReplyRequiredException will be thrown when - the underlying service returns a null value. - - - - - - - Message Channel to which replies should be sent after being received from an AMQP Queue and - converted. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Base type for the 'inbound-channel-adapter' and 'inbound-gateway' elements. - - - - - - Unique ID for this adapter. - - - - - - - - Message Channel to which error Messages should be sent. - - - - - - - - - - - - - - - - - - - - - - - - - - - MessageConverter to use when receiving AMQP Messages. - - - - - - - - - - - - (or @Bean) syntax. You cannot use the XML namespace support because it requires - attributes such as queue names to be specified on a child element and, as stated above, the - container must not have a MessageListener defined. - ]]> - - - - - - - - - - - Names of the AMQP Queues from which Messages should be consumed (comma-separated list). - - - - - - - - - - - - - - - - - - - - - - - - - - Reference to the Rabbit ConnectionFactory to be used by this component. - - - - - - - - - - - - MessagePropertiesConverter to use when receiving AMQP Messages. - - - - - - - - - - - - - - Attributes for a RabbitTemplate. This does not include the exchange, queue, or routingKey properties - since those may or may not be exposed for configuration depending on what type of component uses this - attribute group. This group also does not include any of the properties that are shared with the - SimpleMessageListenerContainer, such as channelTransacted, connectionFactory, and - messagePropertiesConverter. - - - - - - Flag to indicate that channels created by this component will be transactional. - Only applies to messages sent to this channel, or when 'message-driven' is 'false'. - - - - - - - The encoding to use when converting between byte arrays and Strings in message properties. - - - - - - - Reference to a MessageConverter to be used by this RabbitTemplate. - - - - - - - - - - - - - - Attributes for a SimpleMessageListenerContainer's properties other than queues, queueNames, - messageListener, and - autoStartup which may or may not be exposed for configuration depending on what type of component uses - this attribute group. - This group also does not include any of the properties that are shared with RabbitTemplate, such as - channelTransacted, - connectionFactory, and messagePropertiesConverter. - - - - - - Flag to indicate that channels created by this component will be transactional. - Only applies to messages received from this channel when 'message-driven' is 'true'. - - - - - - - Acknowledge Mode for the MessageListenerContainer; default 'AUTO' - meaning the adapter automatically acknowledges the message(s) - according to the batch-size. - - - - - - - - - - - - - - Array of AOP Advice instances to be applied to the MessageListener. - - - - - - - - Specify the number of concurrent consumers to create. Default is 1. - Raising the number of concurrent consumers is recommended in order to scale the consumption of - messages coming in - from a queue. However, note that any ordering guarantees are lost once multiple consumers are - registered. In - general, use 1 consumer for low-volume queues. Mutually exclusive with 'consumers-per-queue'. - - - - - - - Specify the number of consumers to create for each queue. - Setting this attribute creates a 'DirectMessageListenerContainer' instead of the default - 'SimpleMessageListenerContainer'. Refer to the Spring AMQP reference documentation for - more information about these containers. - - - - - - - ErrorHandler to be configured on the underlying MessageListener container. - - - - - - - - - - - - - Set whether to expose the listener Rabbit Channel to a registered ChannelAwareMessageListener as - well as - to RabbitTemplate calls. - - - - - - - - - Specifies how many messages to send to each consumer in a single request. Often this can be set - quite high - to improve throughput. It should be greater than or equal to the batch-size value. - - - - - - - - - The timeout for each attempt by a consumer to receive the next message. - Not allowed when 'consumers-per-queue' is set. - - - - - - - - - The interval between recovery attempts, in milliseconds. The default is 5000 ms, that is, 5 - seconds. - - - - - - - - - The time to wait for workers in milliseconds after the container is stopped, and before the - connection is forced closed. - - - - - - - - - Reference to the Executor to be used for running Consumer threads. - - - - - - - - - - - - The TransactionAttribute to use when the Consumer receives the AMQP Message and the Listener is - invoked - within a transaction. This is only applicable when a TransactionManager has been configured. - - - - - - - - - - - - The PlatformTransactionManager to use when the Consumer receives the AMQP Message and the - Listener is invoked. - - - - - - - - - - - - How many messages to process in a single request. - For best results it should be less than or equal to the prefetch count. - Not allowed when 'consumers-per-queue' is set. - - - - - - - - - If 'true', and none of the queues are available on the broker, the container will throw a fatal - exception during - startup and will stop if the queues are deleted when the container is running (after making 3 - attempts to - passively declare the queues). If false, the container will not throw an exception and go into - recovery mode, - attempting to restart according to the 'recovery-interval'. Default 'true' unless - 'consumers-per-queue' is set. - - - - - - - - - - - - - - - Whether headers are mapped before or after conversion from a messaging Message to - a spring amqp Message. Set to true, for example, if you wish to override the - contentType header set by the converter. - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/ChannelTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/ChannelTests-context.xml deleted file mode 100644 index c26e7c7e4f0..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/ChannelTests-context.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/ChannelTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/ChannelTests.java deleted file mode 100644 index d4f49812045..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/ChannelTests.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.MessageListener; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.BlockingQueueConsumer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.support.converter.MessageConversionException; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.amqp.config.AmqpChannelFactoryBean; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -@SpringJUnitConfig -@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) -public class ChannelTests implements RabbitTestContainer { - - static final String QUEUE_POLLABLE_WITH_EP = "pollableWithEP"; - - static final String QUEUE_WITH_EP = "withEP"; - - static final String QUEUE_CONVERT_FAIL = "testConvertFail"; - - @BeforeAll - static void initQueue() throws IOException, InterruptedException { - for (String queue : List.of(QUEUE_POLLABLE_WITH_EP, QUEUE_WITH_EP, QUEUE_CONVERT_FAIL)) { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + queue); - } - } - - @AfterAll - static void deleteQueue() throws IOException, InterruptedException { - for (String queue : List.of(QUEUE_POLLABLE_WITH_EP, QUEUE_WITH_EP, QUEUE_CONVERT_FAIL)) { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + queue); - } - } - - @Autowired - private PublishSubscribeAmqpChannel channel; - - @Autowired - private PollableAmqpChannel pollableWithEP; - - @Autowired - private PointToPointSubscribableAmqpChannel withEP; - - @Autowired - private PublishSubscribeAmqpChannel pubSubWithEP; - - @Autowired - private PollableChannel out; - - @Autowired - private CachingConnectionFactory connectionFactory; - - @Autowired - private AmqpHeaderMapper mapperIn; - - @Autowired - private AmqpHeaderMapper mapperOut; - - @Test - public void pubSubLostConnectionTest() throws Exception { - final CyclicBarrier latch = new CyclicBarrier(2); - channel.subscribe(message -> { - try { - latch.await(10, TimeUnit.SECONDS); - } - catch (Exception e) { - } - }); - this.channel.send(new GenericMessage<>("foo")); - latch.await(10, TimeUnit.SECONDS); - latch.reset(); - BlockingQueueConsumer consumer = (BlockingQueueConsumer) TestUtils.getPropertyValue(this.channel, - "container.consumers", Set.class).iterator().next(); - connectionFactory.destroy(); - waitForNewConsumer(this.channel, consumer); - this.channel.send(new GenericMessage<>("bar")); - latch.await(10, TimeUnit.SECONDS); - this.channel.destroy(); - this.pubSubWithEP.destroy(); - this.withEP.destroy(); - this.pollableWithEP.destroy(); - assertThat(TestUtils.getPropertyValue(connectionFactory, "connectionListener.delegates", Collection.class) - .size()).isEqualTo(0); - } - - @SuppressWarnings("unchecked") - private void waitForNewConsumer(PublishSubscribeAmqpChannel channel, BlockingQueueConsumer consumer) - throws Exception { - - Lock consumersLock = TestUtils.getPropertyValue(channel, "container.consumersLock", Lock.class); - int n = 0; - while (n++ < 100) { - Set consumers = TestUtils - .getPropertyValue(channel, "container.consumers", Set.class); - consumersLock.lock(); - try { - if (!consumers.isEmpty()) { - BlockingQueueConsumer newConsumer = consumers.iterator().next(); - if (newConsumer != consumer && newConsumer.getConsumerTags().size() > 0) { - break; - } - } - } - finally { - consumersLock.unlock(); - } - Thread.sleep(100); - } - assertThat(n < 100).as("Failed to restart consumer").isTrue(); - } - - /* - * Verify queue is declared if not present and not declared if it is already present. - */ - @Test - public void channelDeclarationTests() { - RabbitAdmin admin = new RabbitAdmin(this.connectionFactory); - admin.deleteQueue("implicit"); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(this.connectionFactory); - container.setAutoStartup(false); - AmqpTemplate amqpTemplate = mock(AmqpTemplate.class); - PointToPointSubscribableAmqpChannel channel = new PointToPointSubscribableAmqpChannel("implicit", container, - amqpTemplate); - channel.setBeanFactory(mock(BeanFactory.class)); - channel.afterPropertiesSet(); - channel.onCreate(null); - - assertThat(admin.getQueueProperties("implicit")).isNotNull(); - - admin.deleteQueue("explicit"); - channel.setQueueName("explicit"); - channel.afterPropertiesSet(); - channel.onCreate(null); - - assertThat(admin.getQueueProperties("explicit")).isNotNull(); - - admin.deleteQueue("explicit"); - admin.declareQueue(new Queue("explicit", false)); // verify no declaration if exists with non-standard props - channel.afterPropertiesSet(); - channel.onCreate(null); - - assertThat(admin.getQueueProperties("explicit")).isNotNull(); - admin.deleteQueue("explicit"); - } - - @Test - public void testAmqpChannelFactoryBean() throws Exception { - AmqpChannelFactoryBean channelFactoryBean = new AmqpChannelFactoryBean(); - channelFactoryBean.setBeanFactory(mock(BeanFactory.class)); - channelFactoryBean.setConnectionFactory(this.connectionFactory); - channelFactoryBean.setBeanName("testChannel"); - channelFactoryBean.afterPropertiesSet(); - AbstractAmqpChannel channel = channelFactoryBean.getObject(); - assertThat(channel).isInstanceOf(PointToPointSubscribableAmqpChannel.class); - - channelFactoryBean = new AmqpChannelFactoryBean(); - channelFactoryBean.setBeanFactory(mock(BeanFactory.class)); - channelFactoryBean.setConnectionFactory(this.connectionFactory); - channelFactoryBean.setBeanName("testChannel"); - channelFactoryBean.setPubSub(true); - channelFactoryBean.afterPropertiesSet(); - channel = channelFactoryBean.getObject(); - assertThat(channel).isInstanceOf(PublishSubscribeAmqpChannel.class); - - RabbitAdmin rabbitAdmin = new RabbitAdmin(this.connectionFactory); - rabbitAdmin.deleteQueue("testChannel"); - rabbitAdmin.deleteExchange("si.fanout.testChannel"); - } - - @Test - public void extractPayloadTests() { - Foo foo = new Foo("bar"); - Message message = MessageBuilder.withPayload(foo).setHeader("baz", "qux").build(); - this.pollableWithEP.send(message); - Message received = this.pollableWithEP.receive(10000); - assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo(foo); - assertThat(received.getHeaders().get("baz")).isEqualTo("qux"); - - this.withEP.send(message); - received = this.out.receive(10000); - assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo(foo); - assertThat(received.getHeaders().get("baz")).isEqualTo("qux"); - - this.pubSubWithEP.send(message); - received = this.out.receive(10000); - assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo(foo); - assertThat(received.getHeaders().get("baz")).isEqualTo("qux"); - - assertThat(TestUtils.getPropertyValue(this.pollableWithEP, "inboundHeaderMapper")).isSameAs(this.mapperIn); - assertThat(TestUtils.getPropertyValue(this.pollableWithEP, "outboundHeaderMapper")).isSameAs(this.mapperOut); - } - - @Test - public void messageConversionTests() { - RabbitTemplate amqpTemplate = new RabbitTemplate(this.connectionFactory); - MessageConverter messageConverter = mock(MessageConverter.class); - amqpTemplate.setMessageConverter(messageConverter); - PointToPointSubscribableAmqpChannel channel = new PointToPointSubscribableAmqpChannel("testConvertFail", - new SimpleMessageListenerContainer(this.connectionFactory), amqpTemplate); - channel.setBeanFactory(mock()); - channel.afterPropertiesSet(); - MessageListener listener = TestUtils.getPropertyValue(channel, "container.messageListener", - MessageListener.class); - willThrow(new MessageConversionException("foo", new IllegalStateException("bar"))) - .given(messageConverter).fromMessage(any(org.springframework.amqp.core.Message.class)); - assertThatExceptionOfType(MessageConversionException.class) - .isThrownBy(() -> listener.onMessage(mock(org.springframework.amqp.core.Message.class))) - .withCauseInstanceOf(IllegalStateException.class); - } - - public static class Foo { - - private String bar; - - public Foo() { - } - - public Foo(String bar) { - this.bar = bar; - } - - public String getBar() { - return this.bar; - } - - public void setBar(String bar) { - this.bar = bar; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.bar == null) ? 0 : this.bar.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Foo other = (Foo) obj; - if (this.bar == null) { - return other.bar == null; - } - else { - return this.bar.equals(other.bar); - } - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/DispatcherHasNoSubscribersTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/DispatcherHasNoSubscribersTests.java deleted file mode 100644 index 9335bcdc8c3..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/channel/DispatcherHasNoSubscribersTests.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.channel; - -import com.rabbitmq.client.AMQP.Queue.DeclareOk; -import com.rabbitmq.client.Channel; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageListener; -import org.springframework.amqp.rabbit.connection.Connection; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; -import org.springframework.beans.factory.BeanFactory; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - * - */ -public class DispatcherHasNoSubscribersTests { - - @Test - public void testPtP() throws Exception { - final Channel channel = mock(); - DeclareOk declareOk = mock(); - when(declareOk.getQueue()).thenReturn("noSubscribersChannel"); - when(channel.queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), isNull())) - .thenReturn(declareOk); - Connection connection = mock(Connection.class); - doAnswer(invocation -> channel).when(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - AmqpTemplate amqpTemplate = mock(AmqpTemplate.class); - - PointToPointSubscribableAmqpChannel amqpChannel = - new PointToPointSubscribableAmqpChannel("noSubscribersChannel", container, amqpTemplate); - amqpChannel.setBeanName("noSubscribersChannel"); - amqpChannel.setBeanFactory(mock(BeanFactory.class)); - amqpChannel.afterPropertiesSet(); - - MessageListener listener = container.getMessageListener(); - - assertThatExceptionOfType(ListenerExecutionFailedException.class) - .isThrownBy(() -> listener.onMessage(new Message("Hello world!".getBytes()))) - .withMessageContaining("Dispatcher has no subscribers for amqp-channel 'noSubscribersChannel'."); - } - - @Test - public void testPubSub() { - final Channel channel = mock(); - Connection connection = mock(); - doAnswer(invocation -> channel).when(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - AmqpTemplate amqpTemplate = mock(AmqpTemplate.class); - PublishSubscribeAmqpChannel amqpChannel = - new PublishSubscribeAmqpChannel("noSubscribersChannel", container, amqpTemplate); - amqpChannel.setBeanName("noSubscribersChannel"); - amqpChannel.setBeanFactory(mock(BeanFactory.class)); - amqpChannel.afterPropertiesSet(); - - MessageListener listener = container.getMessageListener(); - assertThatExceptionOfType(ListenerExecutionFailedException.class) - .isThrownBy(() -> listener.onMessage(new Message("Hello world!".getBytes()))) - .withMessageContaining("Dispatcher has no subscribers for amqp-channel 'noSubscribersChannel'."); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpChannelParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpChannelParserTests-context.xml deleted file mode 100644 index 39502496769..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpChannelParserTests-context.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpChannelParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpChannelParserTests.java deleted file mode 100644 index 149299f5e8a..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpChannelParserTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.integration.amqp.channel.AbstractAmqpChannel; -import org.springframework.integration.amqp.channel.PointToPointSubscribableAmqpChannel; -import org.springframework.integration.amqp.channel.PollableAmqpChannel; -import org.springframework.integration.amqp.channel.PublishSubscribeAmqpChannel; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Gary Russell - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpChannelParserTests { - - @Autowired - private ApplicationContext context; - - @Autowired - private PollableAmqpChannel pollableWithEP; - - @Autowired - private PointToPointSubscribableAmqpChannel withEP; - - @Autowired - private PublishSubscribeAmqpChannel pubSubWithEP; - - @Test - public void interceptor() { - MessageChannel channel = context.getBean("channelWithInterceptor", MessageChannel.class); - List interceptorList = TestUtils.getPropertyValue(channel, "interceptors.interceptors", List.class); - assertThat(interceptorList.size()).isEqualTo(1); - assertThat(interceptorList.get(0).getClass()).isEqualTo(TestInterceptor.class); - assertThat(TestUtils.getPropertyValue( - TestUtils.getPropertyValue(channel, "dispatcher"), "maxSubscribers", Integer.class).intValue()) - .isEqualTo(Integer.MAX_VALUE); - channel = context.getBean("pubSub", MessageChannel.class); - Object mbf = context.getBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME); - assertThat(TestUtils.getPropertyValue(channel, "container.messageListener.messageBuilderFactory")) - .isSameAs(mbf); - assertThat(TestUtils.getPropertyValue(channel, "container.missingQueuesFatal", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(channel, "container.transactional", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(channel, "amqpTemplate.transactional", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(channel, "container")).isInstanceOf(SimpleMessageListenerContainer.class); - } - - @Test - public void subscriberLimit() { - MessageChannel channel = context.getBean("channelWithSubscriberLimit", MessageChannel.class); - assertThat(TestUtils.getPropertyValue( - TestUtils.getPropertyValue(channel, "dispatcher"), "maxSubscribers", Integer.class).intValue()) - .isEqualTo(1); - assertThat(TestUtils.getPropertyValue(channel, "container.missingQueuesFatal", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(channel, "container.transactional", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(channel, "amqpTemplate.transactional", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(channel, "extractPayload", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(channel, "container")).isInstanceOf(DirectMessageListenerContainer.class); - assertThat(TestUtils.getPropertyValue(channel, "container.consumersPerQueue")).isEqualTo(2); - } - - @Test - public void testMapping() { - checkExtract(this.pollableWithEP); - checkExtract(this.withEP); - checkExtract(this.pubSubWithEP); - assertThat(TestUtils.getPropertyValue(this.withEP, "defaultDeliveryMode")) - .isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(TestUtils.getPropertyValue(this.withEP, "headersMappedLast", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.pollableWithEP, "defaultDeliveryMode")).isNull(); - assertThat(TestUtils.getPropertyValue(this.pollableWithEP, "headersMappedLast", Boolean.class)).isTrue(); - } - - private void checkExtract(AbstractAmqpChannel channel) { - assertThat(TestUtils.getPropertyValue(channel, "outboundHeaderMapper").toString()) - .contains("Mock for AmqpHeaderMapper"); - assertThat(TestUtils.getPropertyValue(channel, "inboundHeaderMapper").toString()) - .contains("Mock for AmqpHeaderMapper"); - assertThat(TestUtils.getPropertyValue(channel, "extractPayload", Boolean.class)).isTrue(); - } - - private static class TestInterceptor implements ChannelInterceptor { - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests-context.xml deleted file mode 100644 index b437b615572..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - NONE - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests-headerMapper-fail-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests-headerMapper-fail-context.xml deleted file mode 100644 index 86f075d2694..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests-headerMapper-fail-context.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests.java deleted file mode 100644 index 573f0505a59..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundChannelAdapterParserTests.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter.BatchMode; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mock; - -/** - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpInboundChannelAdapterParserTests { - - @Autowired - private ApplicationContext context; - - @Test - public void verifyIdAsChannel() { - Object channel = context.getBean("rabbitInbound"); - Object adapter = context.getBean("rabbitInbound.adapter"); - assertThat(channel.getClass()).isEqualTo(DirectChannel.class); - assertThat(adapter.getClass()).isEqualTo(AmqpInboundChannelAdapter.class); - assertThat(TestUtils.getPropertyValue(adapter, "autoStartup")).isEqualTo(Boolean.TRUE); - assertThat(TestUtils.getPropertyValue(adapter, "phase")).isEqualTo(Integer.MAX_VALUE / 2); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.missingQueuesFatal", Boolean.class)) - .isTrue(); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer")) - .isInstanceOf(SimpleMessageListenerContainer.class); - assertThat(TestUtils.getPropertyValue(adapter, "batchMode", BatchMode.class)) - .isEqualTo(BatchMode.EXTRACT_PAYLOADS); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.batchSize", Integer.class)) - .isEqualTo(2); - } - - @Test - public void verifyDMCC() { - Object adapter = context.getBean("dmlc.adapter"); - assertThat(adapter.getClass()).isEqualTo(AmqpInboundChannelAdapter.class); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.missingQueuesFatal", Boolean.class)) - .isFalse(); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer")) - .isInstanceOf(DirectMessageListenerContainer.class); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.consumersPerQueue")).isEqualTo(2); - assertThat(TestUtils.getPropertyValue(adapter, "batchMode", BatchMode.class)) - .isEqualTo(BatchMode.MESSAGES); - } - - @Test - public void verifyLifeCycle() { - Object adapter = context.getBean("autoStartFalse.adapter"); - assertThat(TestUtils.getPropertyValue(adapter, "autoStartup")).isEqualTo(Boolean.FALSE); - assertThat(TestUtils.getPropertyValue(adapter, "phase")).isEqualTo(123); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.acknowledgeMode")) - .isEqualTo(AcknowledgeMode.NONE); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.missingQueuesFatal", Boolean.class)) - .isFalse(); - assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer.batchSize", Integer.class)) - .isEqualTo(3); - } - - @Test - public void withHeaderMapperStandardAndCustomHeaders() throws Exception { - AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperStandardAndCustomHeaders", - AmqpInboundChannelAdapter.class); - - AbstractMessageListenerContainer mlc = - TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class); - ChannelAwareMessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", - ChannelAwareMessageListener.class); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentEncoding("test.contentEncoding"); - amqpProperties.setContentLength(99L); - amqpProperties.setContentType("test.contentType"); - amqpProperties.setHeader("foo", "foo"); - amqpProperties.setHeader("bar", "bar"); - Message amqpMessage = new Message("hello".getBytes(), amqpProperties); - listener.onMessage(amqpMessage, mock()); - QueueChannel requestChannel = context.getBean("requestChannel", QueueChannel.class); - org.springframework.messaging.Message siMessage = requestChannel.receive(0); - assertThat(siMessage.getHeaders().get("foo")).isEqualTo("foo"); - assertThat(siMessage.getHeaders().get("bar")).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_ENCODING)).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CLUSTER_ID)).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNotNull(); - } - - @Test - public void withHeaderMapperOnlyCustomHeaders() throws Exception { - AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperOnlyCustomHeaders", - AmqpInboundChannelAdapter.class); - - AbstractMessageListenerContainer mlc = - TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class); - ChannelAwareMessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", - ChannelAwareMessageListener.class); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentEncoding("test.contentEncoding"); - amqpProperties.setContentLength(99L); - amqpProperties.setContentType("test.contentType"); - amqpProperties.setHeader("foo", "foo"); - amqpProperties.setHeader("bar", "bar"); - Message amqpMessage = new Message("hello".getBytes(), amqpProperties); - listener.onMessage(amqpMessage, mock()); - QueueChannel requestChannel = context.getBean("requestChannel", QueueChannel.class); - org.springframework.messaging.Message siMessage = requestChannel.receive(0); - assertThat(siMessage.getHeaders().get("foo")).isEqualTo("foo"); - assertThat(siMessage.getHeaders().get("bar")).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_ENCODING)).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CLUSTER_ID)).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNull(); - } - - @Test - public void withHeaderMapperNothingToMap() throws Exception { - AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperNothingToMap", - AmqpInboundChannelAdapter.class); - - AbstractMessageListenerContainer mlc = - TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class); - ChannelAwareMessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", - ChannelAwareMessageListener.class); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentEncoding("test.contentEncoding"); - amqpProperties.setContentLength(99L); - amqpProperties.setContentType("test.contentType"); - amqpProperties.setHeader("foo", "foo"); - amqpProperties.setHeader("bar", "bar"); - Message amqpMessage = new Message("hello".getBytes(), amqpProperties); - listener.onMessage(amqpMessage, mock()); - - QueueChannel requestChannel = context.getBean("requestChannel", QueueChannel.class); - org.springframework.messaging.Message siMessage = requestChannel.receive(0); - assertThat(siMessage.getHeaders().get("foo")).isNull(); - assertThat(siMessage.getHeaders().get("bar")).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_ENCODING)).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CLUSTER_ID)).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNull(); - } - - @Test - public void withHeaderMapperDefaultMapping() throws Exception { - AmqpInboundChannelAdapter adapter = context.getBean("withHeaderMapperDefaultMapping", - AmqpInboundChannelAdapter.class); - - AbstractMessageListenerContainer mlc = - TestUtils.getPropertyValue(adapter, "messageListenerContainer", AbstractMessageListenerContainer.class); - ChannelAwareMessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", - ChannelAwareMessageListener.class); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentEncoding("test.contentEncoding"); - amqpProperties.setContentLength(99L); - amqpProperties.setContentType("test.contentType"); - amqpProperties.setHeader("foo", "foo"); - amqpProperties.setHeader("bar", "bar"); - Message amqpMessage = new Message("hello".getBytes(), amqpProperties); - listener.onMessage(amqpMessage, mock()); - QueueChannel requestChannel = context.getBean("requestChannel", QueueChannel.class); - org.springframework.messaging.Message siMessage = requestChannel.receive(0); - assertThat(siMessage.getHeaders().get("bar")).isNotNull(); - assertThat(siMessage.getHeaders().get("foo")).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_ENCODING)).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CLUSTER_ID)).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNotNull(); - assertThat(siMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNotNull(); - } - - @Test - public void testInt2971HeaderMapperAndMappedHeadersExclusivity() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext( - "AmqpInboundChannelAdapterParserTests-headerMapper-fail-context.xml", - getClass())) - .withMessageStartingWith("Configuration problem: The 'header-mapper' attribute " + - "is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests-context.xml deleted file mode 100644 index 23be1fb6595..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests-context.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests-headerMapper-fail-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests-headerMapper-fail-context.xml deleted file mode 100644 index 04eb20c77f1..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests-headerMapper-fail-context.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests.java deleted file mode 100644 index 81d1a631ef5..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpInboundGatewayParserTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import java.lang.reflect.Field; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.amqp.core.Address; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.amqp.inbound.AmqpInboundGateway; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; - -/** - * @author Mark Fisher - * @author Gunnar Hillert - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpInboundGatewayParserTests { - - @Autowired - private ApplicationContext context; - - @Test - public void customMessageConverter() { - Object gateway = context.getBean("gateway"); - MessageConverter gatewayConverter = - TestUtils.getPropertyValue(gateway, "amqpMessageConverter", MessageConverter.class); - MessageConverter templateConverter = - TestUtils.getPropertyValue(gateway, "amqpTemplate.messageConverter", MessageConverter.class); - TestConverter testConverter = context.getBean("testConverter", TestConverter.class); - assertThat(gatewayConverter).isSameAs(testConverter); - assertThat(templateConverter).isSameAs(testConverter); - assertThat(TestUtils.getPropertyValue(gateway, "autoStartup")).isEqualTo(Boolean.TRUE); - assertThat(TestUtils.getPropertyValue(gateway, "phase")).isEqualTo(0); - assertThat(TestUtils.getPropertyValue(gateway, "messagingTemplate.receiveTimeout")).isEqualTo(1234L); - assertThat(TestUtils.getPropertyValue(gateway, "messageListenerContainer.missingQueuesFatal", Boolean.class)) - .isTrue(); - } - - @Test - public void verifyLifeCycle() { - Object gateway = context.getBean("autoStartFalseGateway"); - assertThat(TestUtils.getPropertyValue(gateway, "autoStartup")).isEqualTo(Boolean.FALSE); - assertThat(TestUtils.getPropertyValue(gateway, "phase")).isEqualTo(123); - assertThat(TestUtils.getPropertyValue(gateway, "messageListenerContainer.missingQueuesFatal", Boolean.class)) - .isFalse(); - Object amqpTemplate = context.getBean("amqpTemplate"); - assertThat(TestUtils.getPropertyValue(gateway, "amqpTemplate")).isSameAs(amqpTemplate); - Address defaultReplyTo = TestUtils.getPropertyValue(gateway, "defaultReplyTo", Address.class); - Address expected = new Address("fooExchange/barRoutingKey"); - assertThat(defaultReplyTo.getExchangeName()).isEqualTo(expected.getExchangeName()); - assertThat(defaultReplyTo.getRoutingKey()).isEqualTo(expected.getRoutingKey()); - assertThat(defaultReplyTo).isEqualTo(expected); - } - - @Test - public void verifyUsageWithHeaderMapper() throws Exception { - DirectChannel requestChannel = context.getBean("requestChannel", DirectChannel.class); - requestChannel.subscribe(siMessage -> { - org.springframework.messaging.Message replyMessage = MessageBuilder.fromMessage(siMessage) - .setHeader("bar", "bar").build(); - MessageChannel replyChannel = (MessageChannel) siMessage.getHeaders().getReplyChannel(); - replyChannel.send(replyMessage); - }); - - final AmqpInboundGateway gateway = context.getBean("withHeaderMapper", AmqpInboundGateway.class); - assertThat(TestUtils.getPropertyValue(gateway, "replyHeadersMappedLast", Boolean.class)).isTrue(); - Field amqpTemplateField = ReflectionUtils.findField(AmqpInboundGateway.class, "amqpTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(gateway, "amqpTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - Message amqpReplyMessage = (Message) args[2]; - MessageProperties properties = amqpReplyMessage.getMessageProperties(); - assertThat(properties.getHeaders().get("bar")).isEqualTo("bar"); - return null; - }).when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(Message.class), isNull()); - ReflectionUtils.setField(amqpTemplateField, gateway, amqpTemplate); - - AbstractMessageListenerContainer mlc = - TestUtils.getPropertyValue(gateway, "messageListenerContainer", AbstractMessageListenerContainer.class); - ChannelAwareMessageListener listener = TestUtils.getPropertyValue(mlc, "messageListener", - ChannelAwareMessageListener.class); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentEncoding("test.contentEncoding"); - amqpProperties.setContentLength(99L); - amqpProperties.setReplyTo("oleg"); - amqpProperties.setContentType("test.contentType"); - amqpProperties.setHeader("foo", "foo"); - amqpProperties.setHeader("bar", "bar"); - Message amqpMessage = new Message("hello".getBytes(), amqpProperties); - listener.onMessage(amqpMessage, mock()); - - Mockito.verify(amqpTemplate, Mockito.times(1)).send(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(Message.class), isNull()); - } - - @Test - public void testInt2971HeaderMapperAndMappedHeadersExclusivity() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("AmqpInboundGatewayParserTests-headerMapper-fail-context.xml", - getClass())) - .withMessageStartingWith("Configuration problem: The 'header-mapper' attribute " + - "is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"); - } - - private static class TestConverter extends SimpleMessageConverter { - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index e101b6cabe2..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-headerMapper-fail-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-headerMapper-fail-context.xml deleted file mode 100644 index def0f5f93b2..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-headerMapper-fail-context.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java deleted file mode 100644 index d9a5207dafc..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import java.io.IOException; -import java.lang.reflect.Field; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.Channel; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.connection.Connection; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.connection.PublisherCallbackChannelImpl; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.beans.BeansException; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.context.NamedComponent; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Gunnar Hillert - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpOutboundChannelAdapterParserTests { - - private static volatile int adviceCalled; - - @Autowired - private ApplicationContext context; - - @Autowired - @Qualifier("withCustomHeaderMapper.handler") - private MessageHandler amqpMessageHandlerWithCustomHeaderMapper; - - @Test - public void verifyIdAsChannel() { - Object channel = context.getBean("rabbitOutbound"); - Object adapter = context.getBean("rabbitOutbound.adapter"); - assertThat(channel.getClass()).isEqualTo(DirectChannel.class); - assertThat(adapter.getClass()).isEqualTo(EventDrivenConsumer.class); - MessageHandler handler = TestUtils.getPropertyValue(adapter, "handler", MessageHandler.class); - assertThat(handler instanceof NamedComponent).isTrue(); - assertThat(((NamedComponent) handler).getComponentType()).isEqualTo("amqp:outbound-channel-adapter"); - handler.handleMessage(new GenericMessage("foo")); - assertThat(adviceCalled).isEqualTo(1); - assertThat(TestUtils.getPropertyValue(handler, "lazyConnect", Boolean.class)).isTrue(); - } - - @Test - public void withHeaderMapperCustomHeaders() { - Object eventDrivenConsumer = context.getBean("withHeaderMapperCustomHeaders"); - - AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", - AmqpOutboundEndpoint.class); - assertThat(TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode")).isNotNull(); - assertThat(TestUtils.getPropertyValue(endpoint, "lazyConnect", Boolean.class)).isFalse(); - assertThat(TestUtils - .getPropertyValue(endpoint, "delayExpression", org.springframework.expression.Expression.class) - .getExpressionString()).isEqualTo("42"); - assertThat(TestUtils.getPropertyValue(endpoint, "headersMappedLast", Boolean.class)).isFalse(); - - Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "rabbitTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "rabbitTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - final AtomicBoolean shouldBePersistent = new AtomicBoolean(); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - org.springframework.amqp.core.Message amqpMessage = (org.springframework.amqp.core.Message) args[2]; - MessageProperties properties = amqpMessage.getMessageProperties(); - assertThat(properties.getHeaders().get("foo")).isEqualTo("foo"); - assertThat(properties.getHeaders().get("foobar")).isEqualTo("foobar"); - assertThat(properties.getHeaders().get("bar")).isNull(); - assertThat(properties.getDeliveryMode()).isEqualTo(shouldBePersistent.get() ? - MessageDeliveryMode.PERSISTENT - : MessageDeliveryMode.NON_PERSISTENT); - return null; - }) - .when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(org.springframework.amqp.core.Message.class), Mockito.any(CorrelationData.class)); - ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class); - Message message = MessageBuilder.withPayload("hello") - .setHeader("foo", "foo") - .setHeader("bar", "bar") - .setHeader("foobar", "foobar") - .build(); - requestChannel.send(message); - Mockito.verify(amqpTemplate, Mockito.times(1)).send(anyString(), - isNull(), Mockito.any(org.springframework.amqp.core.Message.class), isNull()); - - shouldBePersistent.set(true); - message = MessageBuilder.withPayload("hello") - .setHeader("foo", "foo") - .setHeader("bar", "bar") - .setHeader("foobar", "foobar") - .setHeader(AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.PERSISTENT) - .build(); - requestChannel.send(message); - } - - @Test - public void parseWithPublisherConfirms() { - Object eventDrivenConsumer = context.getBean("withPublisherConfirms"); - AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", - AmqpOutboundEndpoint.class); - NullChannel nullChannel = context.getBean(NullChannel.class); - MessageChannel ackChannel = context.getBean("ackChannel", MessageChannel.class); - assertThat(TestUtils.getPropertyValue(endpoint, "confirmAckChannel")).isSameAs(ackChannel); - assertThat(TestUtils.getPropertyValue(endpoint, "confirmNackChannel")).isSameAs(nullChannel); - assertThat(TestUtils.getPropertyValue(endpoint, "errorMessageStrategy")).isSameAs(context.getBean("ems")); - assertThat(TestUtils.getPropertyValue(endpoint, "waitForConfirm", Boolean.class)).isFalse(); - } - - @Test - public void parseWithPublisherConfirms2() { - Object eventDrivenConsumer = context.getBean("withPublisherConfirms2"); - AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", - AmqpOutboundEndpoint.class); - MessageChannel nackChannel = context.getBean("nackChannel", MessageChannel.class); - MessageChannel ackChannel = context.getBean("ackChannel", MessageChannel.class); - assertThat(TestUtils.getPropertyValue(endpoint, "confirmAckChannel")).isSameAs(ackChannel); - assertThat(TestUtils.getPropertyValue(endpoint, "confirmNackChannel")).isSameAs(nackChannel); - assertThat(TestUtils.getPropertyValue(endpoint, "confirmTimeout")).isEqualTo(Duration.ofMillis(2000)); - assertThat(TestUtils.getPropertyValue(endpoint, "errorMessageStrategy")).isSameAs(context.getBean("ems")); - assertThat(TestUtils.getPropertyValue(endpoint, "waitForConfirm", Boolean.class)).isTrue(); - } - - @SuppressWarnings("rawtypes") - @Test - public void amqpOutboundChannelAdapterWithinChain() { - Object eventDrivenConsumer = context.getBean("chainWithRabbitOutbound"); - - List chainHandlers = TestUtils.getPropertyValue(eventDrivenConsumer, "handler.handlers", List.class); - - AmqpOutboundEndpoint endpoint = (AmqpOutboundEndpoint) chainHandlers.get(0); - assertThat(TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode")).isNull(); - - Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "rabbitTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "rabbitTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - org.springframework.amqp.core.Message amqpMessage = (org.springframework.amqp.core.Message) args[2]; - MessageProperties properties = amqpMessage.getMessageProperties(); - assertThat(new String(amqpMessage.getBody())).isEqualTo("hello"); - assertThat(properties.getDeliveryMode()).isEqualTo(MessageDeliveryMode.PERSISTENT); - return null; - }) - .when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(org.springframework.amqp.core.Message.class), - Mockito.any(CorrelationData.class)); - ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - MessageChannel requestChannel = context.getBean("amqpOutboundChannelAdapterWithinChain", MessageChannel.class); - Message message = MessageBuilder.withPayload("hello").build(); - requestChannel.send(message); - Mockito.verify(amqpTemplate, Mockito.times(1)).send(Mockito.any(String.class), - isNull(), Mockito.any(org.springframework.amqp.core.Message.class), isNull()); - } - - @Test - public void testInt2718FailForOutboundAdapterChannelAttribute() { - try { - new ClassPathXmlApplicationContext("AmqpOutboundChannelAdapterWithinChainParserTests-fail-context.xml", - this.getClass()).close(); - fail("Expected BeanDefinitionParsingException"); - } - catch (BeansException e) { - assertThat(e instanceof BeanDefinitionParsingException).isTrue(); - assertThat(e.getMessage().contains("The 'channel' attribute isn't allowed for " + - "'amqp:outbound-channel-adapter' when it is used as a nested element")).isTrue(); - } - } - - @Test - public void testInt2773UseDefaultAmqpTemplateExchangeAndRoutingLey() throws IOException { - ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); - Connection mockConnection = mock(Connection.class); - Channel mockChannel = mock(Channel.class); - - when(connectionFactory.createConnection()).thenReturn(mockConnection); - PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel, - mock(ExecutorService.class)); - when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel); - - MessageChannel requestChannel = context.getBean("toRabbitOnlyWithTemplateChannel", MessageChannel.class); - requestChannel.send(MessageBuilder.withPayload("test").build()); - Mockito.verify(mockChannel, Mockito.times(1)).basicPublish(Mockito.eq("default.test.exchange"), - Mockito.eq("default.routing.key"), - Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class)); - } - - @Test - public void testInt2773WithDefaultAmqpTemplateExchangeAndRoutingKey() throws IOException { - ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); - Connection mockConnection = mock(Connection.class); - Channel mockChannel = mock(Channel.class); - - when(connectionFactory.createConnection()).thenReturn(mockConnection); - PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel, - mock(ExecutorService.class)); - when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel); - - MessageChannel requestChannel = context.getBean("withDefaultAmqpTemplateExchangeAndRoutingKey", - MessageChannel.class); - requestChannel.send(MessageBuilder.withPayload("test").build()); - Mockito.verify(mockChannel, Mockito.times(1)).basicPublish(Mockito.eq(""), Mockito.eq(""), - Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class)); - } - - @Test - public void testInt2773WithOverrideToDefaultAmqpTemplateExchangeAndRoutingLey() throws IOException { - ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); - Connection mockConnection = mock(Connection.class); - Channel mockChannel = mock(Channel.class); - - when(connectionFactory.createConnection()).thenReturn(mockConnection); - PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel, - mock(ExecutorService.class)); - when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel); - - MessageChannel requestChannel = context.getBean("overrideTemplateAttributesToEmpty", MessageChannel.class); - requestChannel.send(MessageBuilder.withPayload("test").build()); - Mockito.verify(mockChannel, Mockito.times(1)).basicPublish(Mockito.eq(""), Mockito.eq(""), - Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class)); - } - - @Test - public void testInt2971HeaderMapperAndMappedHeadersExclusivity() { - try { - new ClassPathXmlApplicationContext("AmqpOutboundChannelAdapterParserTests-headerMapper-fail-context.xml", - this.getClass()).close(); - } - catch (BeanDefinitionParsingException e) { - assertThat(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " + - "is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'")).isTrue(); - } - } - - @Test - public void testInt2971AmqpOutboundChannelAdapterWithCustomHeaderMapper() { - AmqpHeaderMapper headerMapper = TestUtils.getPropertyValue(this.amqpMessageHandlerWithCustomHeaderMapper, - "headerMapper", AmqpHeaderMapper.class); - assertThat(headerMapper).isSameAs(this.context.getBean("customHeaderMapper")); - assertThat(TestUtils.getPropertyValue(this.amqpMessageHandlerWithCustomHeaderMapper, - "headersMappedLast", Boolean.class)).isTrue(); - } - - @Test - public void testInt3430FailForNotLazyConnect() { - RabbitTemplate amqpTemplate = spy(new RabbitTemplate()); - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - RuntimeException toBeThrown = new RuntimeException("Test Connection Exception"); - doThrow(toBeThrown).when(connectionFactory).createConnection(); - when(amqpTemplate.getConnectionFactory()).thenReturn(connectionFactory); - AmqpOutboundEndpoint handler = new AmqpOutboundEndpoint(amqpTemplate); - LogAccessor logger = spy(TestUtils.getPropertyValue(handler, "logger", LogAccessor.class)); - new DirectFieldAccessor(handler).setPropertyValue("logger", logger); - doNothing().when(logger).error(toBeThrown, "Failed to eagerly establish the connection."); - ApplicationContext context = mock(ApplicationContext.class); - handler.setApplicationContext(context); - handler.setBeanFactory(context); - handler.afterPropertiesSet(); - handler.start(); - handler.stop(); - verify(logger, never()).error(any(RuntimeException.class), anyString()); - handler.setLazyConnect(false); - handler.start(); - verify(logger).error(toBeThrown, "Failed to eagerly establish the connection."); - handler.stop(); - } - - public static class FooAdvice extends AbstractRequestHandlerAdvice { - - @Override - protected Object doInvoke(ExecutionCallback callback, Object target, Message message) { - adviceCalled++; - return null; - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterWithinChainParserTests-fail-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterWithinChainParserTests-fail-context.xml deleted file mode 100644 index 944ae97f737..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterWithinChainParserTests-fail-context.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml deleted file mode 100644 index d809dcc2152..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-headerMapper-fail-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-headerMapper-fail-context.xml deleted file mode 100644 index 01eb76b9e85..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-headerMapper-fail-context.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java deleted file mode 100644 index 81fc4dc38ff..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; -import org.springframework.integration.amqp.outbound.AsyncAmqpOutboundGateway; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.context.Orderable; -import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.isNull; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Gunnar Hillert - * - * @since 2.1 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpOutboundGatewayParserTests { - - private static volatile int adviceCalled; - - @Autowired - private ApplicationContext context; - - @Test - public void testGatewayConfig() { - Object edc = this.context.getBean("rabbitGateway"); - assertThat(TestUtils.getPropertyValue(edc, "autoStartup", Boolean.class)).isFalse(); - AmqpOutboundEndpoint gateway = TestUtils.getPropertyValue(edc, "handler", AmqpOutboundEndpoint.class); - assertThat(gateway.getComponentType()).isEqualTo("amqp:outbound-gateway"); - assertThat(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class)).isTrue(); - checkGWProps(this.context, gateway); - - AsyncAmqpOutboundGateway async = this.context.getBean("asyncGateway.handler", AsyncAmqpOutboundGateway.class); - assertThat(async.getComponentType()).isEqualTo("amqp:outbound-async-gateway"); - checkGWProps(this.context, async); - assertThat(TestUtils.getPropertyValue(async, "template")).isSameAs(this.context.getBean("asyncTemplate")); - assertThat(TestUtils.getPropertyValue(gateway, "errorMessageStrategy")).isSameAs(this.context.getBean("ems")); - } - - protected void checkGWProps(ApplicationContext context, Orderable gateway) { - assertThat(gateway.getOrder()).isEqualTo(5); - assertThat(TestUtils.getPropertyValue(gateway, "outputChannel")).isEqualTo(context.getBean("fromRabbit")); - MessageChannel returnChannel = context.getBean("returnChannel", MessageChannel.class); - assertThat(TestUtils.getPropertyValue(gateway, "returnChannel")).isSameAs(returnChannel); - - Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class); - - assertThat(sendTimeout).isEqualTo(Long.valueOf(777)); - assertThat(TestUtils.getPropertyValue(gateway, "lazyConnect", Boolean.class)).isTrue(); - assertThat(TestUtils - .getPropertyValue(gateway, "delayExpression", org.springframework.expression.Expression.class) - .getExpressionString()).isEqualTo("42"); - } - - @Test - public void withHeaderMapperCustomRequestResponse() { - Object eventDrivenConsumer = this.context.getBean("withHeaderMapperCustomRequestResponse"); - - AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", - AmqpOutboundEndpoint.class); - assertThat(TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode")).isNotNull(); - assertThat(TestUtils.getPropertyValue(endpoint, "lazyConnect", Boolean.class)).isFalse(); - - assertThat(TestUtils.getPropertyValue(endpoint, "requiresReply", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(endpoint, "headersMappedLast", Boolean.class)).isTrue(); - - Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "rabbitTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "rabbitTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - final AtomicBoolean shouldBePersistent = new AtomicBoolean(); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - org.springframework.amqp.core.Message amqpRequestMessage = (org.springframework.amqp.core.Message) args[2]; - MessageProperties properties = amqpRequestMessage.getMessageProperties(); - assertThat(properties.getHeaders().get("foo")).isEqualTo("foo"); - assertThat(properties.getDeliveryMode()).isEqualTo(shouldBePersistent.get() ? - MessageDeliveryMode.PERSISTENT - : MessageDeliveryMode.NON_PERSISTENT); - // mock reply AMQP message - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setHeader("foobar", "foobar"); - amqpProperties.setHeader("bar", "bar"); - return new org.springframework.amqp.core.Message("hello".getBytes(), amqpProperties); - }) - .when(amqpTemplate).sendAndReceive(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(org.springframework.amqp.core.Message.class), isNull()); - ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - MessageChannel requestChannel = this.context.getBean("toRabbit1", MessageChannel.class); - Message message = MessageBuilder.withPayload("hello").setHeader("foo", "foo").build(); - requestChannel.send(message); - - Mockito.verify(amqpTemplate, Mockito.times(1)).sendAndReceive(Mockito.any(String.class), - Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class), - isNull()); - - // verify reply - QueueChannel queueChannel = this.context.getBean("fromRabbit", QueueChannel.class); - Message replyMessage = queueChannel.receive(0); - assertThat(replyMessage).isNotNull(); - assertThat(replyMessage.getHeaders().get("bar")).isEqualTo("bar"); - assertThat(replyMessage.getHeaders().get("foo")).isEqualTo("foo"); // copied from request Message - assertThat(replyMessage.getHeaders().get("foobar")).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE)).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNull(); - - shouldBePersistent.set(true); - message = MessageBuilder.withPayload("hello") - .setHeader("foo", "foo") - .setHeader(AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.PERSISTENT) - .build(); - requestChannel.send(message); - replyMessage = queueChannel.receive(0); - assertThat(replyMessage).isNotNull(); - } - - @Test - public void withHeaderMapperCustomAndStandardResponse() { - Object eventDrivenConsumer = this.context.getBean("withHeaderMapperCustomAndStandardResponse"); - - AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", - AmqpOutboundEndpoint.class); - assertThat(TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode")).isNull(); - assertThat(TestUtils.getPropertyValue(endpoint, "headersMappedLast", Boolean.class)).isFalse(); - - Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "rabbitTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "rabbitTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - org.springframework.amqp.core.Message amqpRequestMessage = (org.springframework.amqp.core.Message) args[2]; - MessageProperties properties = amqpRequestMessage.getMessageProperties(); - assertThat(properties.getHeaders().get("foo")).isEqualTo("foo"); - // mock reply AMQP message - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setHeader("foobar", "foobar"); - amqpProperties.setHeader("bar", "bar"); - assertThat(properties.getDeliveryMode()).isEqualTo(MessageDeliveryMode.PERSISTENT); - amqpProperties.setReceivedDeliveryMode(properties.getDeliveryMode()); - return new org.springframework.amqp.core.Message("hello".getBytes(), amqpProperties); - }) - .when(amqpTemplate).sendAndReceive(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(org.springframework.amqp.core.Message.class), isNull()); - ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - MessageChannel requestChannel = this.context.getBean("toRabbit2", MessageChannel.class); - Message message = MessageBuilder.withPayload("hello").setHeader("foo", "foo").build(); - requestChannel.send(message); - - Mockito.verify(amqpTemplate, Mockito.times(1)).sendAndReceive(Mockito.any(String.class), - Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class), - isNull()); - - // verify reply - QueueChannel queueChannel = this.context.getBean("fromRabbit", QueueChannel.class); - Message replyMessage = queueChannel.receive(0); - assertThat(replyMessage.getHeaders().get("bar")).isEqualTo("bar"); - assertThat(replyMessage.getHeaders().get("foo")).isEqualTo("foo"); // copied from request Message - assertThat(replyMessage.getHeaders().get("foobar")).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.RECEIVED_DELIVERY_MODE)).isNotNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNotNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNotNull(); - } - - @Test - public void withHeaderMapperNothingToMap() { - Object eventDrivenConsumer = this.context.getBean("withHeaderMapperNothingToMap"); - - AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", - AmqpOutboundEndpoint.class); - - Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "rabbitTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "rabbitTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - org.springframework.amqp.core.Message amqpRequestMessage = (org.springframework.amqp.core.Message) args[2]; - MessageProperties properties = amqpRequestMessage.getMessageProperties(); - assertThat(properties.getHeaders().get("foo")).isNull(); - // mock reply AMQP message - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setHeader("foobar", "foobar"); - amqpProperties.setHeader("bar", "bar"); - return new org.springframework.amqp.core.Message("hello".getBytes(), amqpProperties); - }) - .when(amqpTemplate).sendAndReceive(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(org.springframework.amqp.core.Message.class), isNull()); - ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - MessageChannel requestChannel = this.context.getBean("toRabbit3", MessageChannel.class); - Message message = MessageBuilder.withPayload("hello").setHeader("foo", "foo").build(); - requestChannel.send(message); - - Mockito.verify(amqpTemplate, Mockito.times(1)).sendAndReceive(Mockito.any(String.class), - Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class), - isNull()); - - // verify reply - QueueChannel queueChannel = context.getBean("fromRabbit", QueueChannel.class); - Message replyMessage = queueChannel.receive(0); - assertThat(replyMessage.getHeaders().get("bar")).isNull(); - assertThat(replyMessage.getHeaders().get("foo")).isEqualTo("foo"); // copied from request Message - assertThat(replyMessage.getHeaders().get("foobar")).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE)).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNull(); - assertThat(adviceCalled).isEqualTo(1); - } - - @Test //INT-1029 - public void amqpOutboundGatewayWithinChain() { - Object eventDrivenConsumer = this.context.getBean("chainWithRabbitOutboundGateway"); - - List chainHandlers = TestUtils.getPropertyValue(eventDrivenConsumer, "handler.handlers", List.class); - - AmqpOutboundEndpoint endpoint = (AmqpOutboundEndpoint) chainHandlers.get(0); - - Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "rabbitTemplate"); - amqpTemplateField.setAccessible(true); - RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "rabbitTemplate", RabbitTemplate.class); - amqpTemplate = Mockito.spy(amqpTemplate); - - Mockito.doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - org.springframework.amqp.core.Message amqpRequestMessage = (org.springframework.amqp.core.Message) args[2]; - MessageProperties properties = amqpRequestMessage.getMessageProperties(); - assertThat(properties.getHeaders().get("foo")).isNull(); - // mock reply AMQP message - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setHeader("foobar", "foobar"); - amqpProperties.setHeader("bar", "bar"); - return new org.springframework.amqp.core.Message("hello".getBytes(), amqpProperties); - }) - .when(amqpTemplate).sendAndReceive(Mockito.any(String.class), Mockito.any(String.class), - Mockito.any(org.springframework.amqp.core.Message.class), isNull()); - ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - MessageChannel requestChannel = this.context.getBean("toRabbit4", MessageChannel.class); - Message message = MessageBuilder.withPayload("hello").setHeader("foo", "foo").build(); - requestChannel.send(message); - - Mockito.verify(amqpTemplate, Mockito.times(1)).sendAndReceive(Mockito.any(String.class), - Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class), - isNull()); - - // verify reply - QueueChannel queueChannel = this.context.getBean("fromRabbit", QueueChannel.class); - Message replyMessage = queueChannel.receive(0); - assertThat(new String((byte[]) replyMessage.getPayload())).isEqualTo("hello"); - assertThat(replyMessage.getHeaders().get("bar")).isNull(); - assertThat(replyMessage.getHeaders().get("foo")).isEqualTo("foo"); // copied from request Message - assertThat(replyMessage.getHeaders().get("foobar")).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE)).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)).isNull(); - assertThat(replyMessage.getHeaders().get(AmqpHeaders.APP_ID)).isNull(); - - } - - @Test - public void testInt2971HeaderMapperAndMappedHeadersExclusivity() { - try { - new ClassPathXmlApplicationContext("AmqpOutboundGatewayParserTests-headerMapper-fail-context.xml", - this.getClass()).close(); - } - catch (BeanDefinitionParsingException e) { - assertThat(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " + - "is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'")).isTrue(); - } - } - - public static class FooAdvice extends AbstractRequestHandlerAdvice { - - @Override - protected Object doInvoke(ExecutionCallback callback, Object target, Message message) { - adviceCalled++; - return callback.execute(); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayIntegrationTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayIntegrationTests-context.xml deleted file mode 100644 index 3394cec7cad..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayIntegrationTests-context.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayIntegrationTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayIntegrationTests.java deleted file mode 100644 index b5699235cbf..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayIntegrationTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class OutboundGatewayIntegrationTests implements RabbitTestContainer { - - @Autowired - private MessageChannel toRabbit; - - @Autowired - private PollableChannel fromRabbit; - - @Test - public void testOutboundInboundGateways() throws Exception { - String payload = "foo"; - this.toRabbit.send(new GenericMessage(payload)); - Message receive = this.fromRabbit.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload.toUpperCase()); - } - - public static class EchoBean { - - String echo(String o) { - return o.toUpperCase(); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests-context.xml deleted file mode 100644 index a49f481d1e4..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests-context.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.java deleted file mode 100644 index a7fdcb2a508..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; -import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.expression.SpelPropertyAccessorRegistrar; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -/** - * @author Mark Fisher - * @author Dave Syer - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class OutboundGatewayTests { - - private static final ExpressionParser PARSER = new SpelExpressionParser(); - - @Autowired - ConfigurableApplicationContext context; - - @Test - public void testVanillaConfiguration() { - assertThat(context.getBeanFactory().containsBeanDefinition("vanilla")).isTrue(); - context.getBean("vanilla"); - } - - @Test - public void testExpressionBasedConfiguration() { - assertThat(context.getBeanFactory().containsBeanDefinition("expression")).isTrue(); - Object target = context.getBean("expression"); - assertThat(ReflectionTestUtils.getField(ReflectionTestUtils.getField(target, "handler"), - "routingKeyGenerator")).isNotNull(); - } - - @Test - @SuppressWarnings("unchecked") - public void testExpressionsBeanResolver() { - ApplicationContext context = mock(ApplicationContext.class); - doAnswer(invocation -> invocation.getArguments()[0] + "bar").when(context).getBean(anyString()); - when(context.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)).thenReturn(true); - when(context.getBean(SpelPropertyAccessorRegistrar.class)) - .thenThrow(NoSuchBeanDefinitionException.class); - IntegrationEvaluationContextFactoryBean integrationEvaluationContextFactoryBean = - new IntegrationEvaluationContextFactoryBean(); - integrationEvaluationContextFactoryBean.setApplicationContext(context); - integrationEvaluationContextFactoryBean.afterPropertiesSet(); - StandardEvaluationContext evalContext = integrationEvaluationContextFactoryBean.getObject(); - when(context.getBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, - StandardEvaluationContext.class)) - .thenReturn(evalContext); - RabbitTemplate template = spy(new RabbitTemplate()); - willReturn(mock(ConnectionFactory.class)).given(template).getConnectionFactory(); - AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(template); - endpoint.setRoutingKeyExpression(PARSER.parseExpression("@foo")); - endpoint.setExchangeNameExpression(PARSER.parseExpression("@bar")); - endpoint.setConfirmCorrelationExpressionString("@baz"); - endpoint.setBeanFactory(context); - endpoint.afterPropertiesSet(); - Message message = new GenericMessage("Hello, world!"); - assertThat(TestUtils.getPropertyValue(endpoint, "routingKeyGenerator", MessageProcessor.class) - .processMessage(message)).isEqualTo("foobar"); - assertThat(TestUtils.getPropertyValue(endpoint, "exchangeNameGenerator", MessageProcessor.class) - .processMessage(message)).isEqualTo("barbar"); - assertThat(TestUtils.getPropertyValue(endpoint, "correlationDataGenerator", MessageProcessor.class) - .processMessage(message)).isEqualTo("bazbar"); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/dsl/AmqpTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/dsl/AmqpTests.java deleted file mode 100644 index af19e87fe6d..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/dsl/AmqpTests.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import com.rabbitmq.stream.ConsumerBuilder; -import com.rabbitmq.stream.Environment; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.amqp.core.AnonymousQueue; -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.AsyncRabbitTemplate; -import org.springframework.amqp.rabbit.annotation.EnableRabbit; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; -import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConversionException; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.Lifecycle; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.amqp.channel.AbstractAmqpChannel; -import org.springframework.integration.amqp.channel.PollableAmqpChannel; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter.BatchMode; -import org.springframework.integration.amqp.inbound.AmqpInboundGateway; -import org.springframework.integration.amqp.support.AmqpHeaderMapper; -import org.springframework.integration.amqp.support.AmqpMessageHeaderErrorMessageStrategy; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.annotation.Publisher; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.config.EnablePublisher; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.IntegrationFlowBuilder; -import org.springframework.integration.dsl.Transformers; -import org.springframework.integration.dsl.context.IntegrationFlowContext; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.StringObjectMapBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.rabbit.stream.listener.ConsumerCustomizer; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * @author Artem Bilan - * @author Gary Russell - * - * @since 5.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpTests implements RabbitTestContainer { - - static final String QUEUE_AMQP_OUTBOND_INPUT = "amqpOutboundInput"; - - static final String QUEUE_AMQP_REPLY_CHANNEL = "amqpReplyChannel"; - - static final String QUEUE_ASYNC_REPLIES = "asyncReplies"; - - static final String QUEUE_DEFAULT_REPLY_TO = "defaultReplyTo"; - - static final String QUEUE_DSL_TEST = "si.dsl.test"; - - static final String QUEUE_DSL_EXCEPTION_TEST_DLQ = "si.dsl.exception.test.dlq"; - - static final String QUEUE_DSL_CONV_EXCEPTION_TEST_DLQ = "si.dsl.conv.exception.test.dlq"; - - static final String QUEUE_TEMPLATE_CHANNEL_TRANSACTED = "testTemplateChannelTransacted"; - - static final String QUEUE_PUBLISHER = "publisherQueue"; - - static final List QUEUE_NAMES = List.of(QUEUE_AMQP_OUTBOND_INPUT, QUEUE_AMQP_REPLY_CHANNEL, - QUEUE_ASYNC_REPLIES, QUEUE_DEFAULT_REPLY_TO, QUEUE_DSL_TEST, QUEUE_DSL_EXCEPTION_TEST_DLQ, - QUEUE_DSL_CONV_EXCEPTION_TEST_DLQ, QUEUE_TEMPLATE_CHANNEL_TRANSACTED, QUEUE_PUBLISHER); - - @Autowired - private ConnectionFactory rabbitConnectionFactory; - - @Autowired - private IntegrationFlowContext integrationFlowContext; - - @Autowired - private AmqpTemplate amqpTemplate; - - @Autowired - @Qualifier("queue") - private Queue amqpQueue; - - @Autowired - @Qualifier("queue2") - private Queue amqpQueue2; - - @Autowired - private AmqpInboundGateway amqpInboundGateway; - - @Autowired(required = false) - @Qualifier("amqpInboundGatewayContainer") - private SimpleMessageListenerContainer amqpInboundGatewayContainer; - - @Autowired - private Lifecycle asyncOutboundGateway; - - @BeforeAll - static void initQueue() throws IOException, InterruptedException { - for (String queue : QUEUE_NAMES) { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + queue); - } - } - - @AfterAll - static void tearDown(ConfigurableApplicationContext context) throws IOException, InterruptedException { - context.stop(); // prevent queues from being redeclared after deletion - for (String queue : QUEUE_NAMES) { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + queue); - } - for (String additionalQueue : List.of("si.dsl.exception.test", "si.dsl.conv.exception.test")) { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + additionalQueue); - } - } - - @Test - public void testAmqpInboundGatewayFlow() { - assertThat(this.amqpInboundGatewayContainer).isNotNull(); - assertThat(TestUtils.getPropertyValue(this.amqpInboundGateway, "amqpTemplate")).isSameAs(this.amqpTemplate); - - Object result = this.amqpTemplate.convertSendAndReceive(this.amqpQueue.getName(), "world"); - assertThat(result).isEqualTo("HELLO WORLD"); - - this.amqpInboundGateway.stop(); - //INTEXT-209 - this.amqpInboundGateway.start(); - - this.amqpTemplate.convertAndSend(this.amqpQueue.getName(), "world"); - ((RabbitTemplate) this.amqpTemplate).setReceiveTimeout(10000); - result = this.amqpTemplate.receiveAndConvert("defaultReplyTo"); - assertThat(result).isEqualTo("HELLO WORLD"); - } - - @Autowired - @Qualifier("amqpOutboundInput") - private MessageChannel amqpOutboundInput; - - @Autowired - @Qualifier("amqpReplyChannel.channel") - private PollableChannel amqpReplyChannel; - - @Test - public void testAmqpOutboundFlow() { - this.amqpOutboundInput.send(MessageBuilder.withPayload("one") - .setHeader("routingKey", "si.dsl.test") - .build()); - this.amqpOutboundInput.send(MessageBuilder.withPayload("two") - .setHeader("routingKey", "si.dsl.test") - .build()); - Message receive = this.amqpReplyChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("[ONE, TWO]"); - - ((Lifecycle) this.amqpOutboundInput).stop(); - } - - @Test - public void testTemplateChannelTransacted() { - IntegrationFlowBuilder flow = IntegrationFlow.from(Amqp.channel("testTemplateChannelTransacted", - this.rabbitConnectionFactory) - .autoStartup(false) - .templateChannelTransacted(true)); - assertThat(TestUtils.getPropertyValue(flow, "currentMessageChannel.amqpTemplate.transactional", - Boolean.class)).isTrue(); - } - - @Autowired - @Qualifier("amqpAsyncOutboundFlow.input") - private MessageChannel amqpAsyncOutboundFlowInput; - - @Test - public void testAmqpAsyncOutboundGatewayFlow() { - QueueChannel replyChannel = new QueueChannel(); - this.amqpAsyncOutboundFlowInput.send(MessageBuilder.withPayload("async gateway") - .setReplyChannel(replyChannel) - .build()); - - Message receive = replyChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("HELLO ASYNC GATEWAY"); - - this.asyncOutboundGateway.stop(); - } - - @Autowired - private AtomicReference lefe; - - @Autowired - private AtomicReference raw; - - @Test - public void testInboundMessagingExceptionFlow() { - this.amqpTemplate.convertAndSend("si.dsl.exception.test", "foo"); - assertThat(this.amqpTemplate.receive("si.dsl.exception.test.dlq", 30_000)).isNotNull(); - assertThat(this.lefe.get()).isNull(); - assertThat(this.raw.get()).isNotNull(); - this.raw.set(null); - } - - @Test - public void testInboundConversionExceptionFlow() { - this.amqpTemplate.convertAndSend("si.dsl.conv.exception.test", "foo"); - assertThat(this.amqpTemplate.receive("si.dsl.conv.exception.test.dlq", 30_000)).isNotNull(); - assertThat(this.lefe.get()).isNotNull(); - assertThat(this.lefe.get().getCause()).isInstanceOf(MessageConversionException.class); - assertThat(this.raw.get()).isNotNull(); - this.raw.set(null); - this.lefe.set(null); - } - - @Autowired - private AbstractAmqpChannel unitChannel; - - @Autowired - private AmqpHeaderMapper mapperIn; - - @Autowired - private AmqpHeaderMapper mapperOut; - - @Test - void unitTestChannel() { - assertThat(TestUtils.getPropertyValue(this.unitChannel, "defaultDeliveryMode")) - .isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(TestUtils.getPropertyValue(this.unitChannel, "inboundHeaderMapper")).isSameAs(this.mapperIn); - assertThat(TestUtils.getPropertyValue(this.unitChannel, "outboundHeaderMapper")).isSameAs(this.mapperOut); - assertThat(TestUtils.getPropertyValue(this.unitChannel, "extractPayload", Boolean.class)).isTrue(); - } - - @Test - void testContentTypeOverrideWithReplyHeadersMappedLast() { - IntegrationFlow testFlow = - IntegrationFlow - .from(Amqp.inboundGateway(this.rabbitConnectionFactory, this.amqpQueue2) - .replyHeadersMappedLast(true)) - .transform(Transformers.fromJson()) - .enrich((enricher) -> enricher.property("REPLY_KEY", "REPLY_VALUE")) - .transform(Transformers.toJson()) - .get(); - - IntegrationFlowContext.IntegrationFlowRegistration registration = - this.integrationFlowContext.registration(testFlow).register(); - - RabbitTemplate rabbitTemplate = new RabbitTemplate(this.rabbitConnectionFactory); - rabbitTemplate.setMessageConverter(new JacksonJsonMessageConverter()); - - Object result = rabbitTemplate.convertSendAndReceive(this.amqpQueue2.getName(), - new HashMap<>(Collections.singletonMap("TEST_KEY", "TEST_VALUE"))); - - assertThat(result).isInstanceOf(Map.class); - - @SuppressWarnings("unchecked") - Map resultMap = (Map) result; - - assertThat(resultMap) - .containsEntry("TEST_KEY", "TEST_VALUE") - .containsEntry("REPLY_KEY", "REPLY_VALUE"); - - registration.destroy(); - } - - @Test - void streamContainer() { - Environment env = mock(Environment.class); - given(env.consumerBuilder()).willReturn(mock(ConsumerBuilder.class)); - RabbitStreamInboundChannelAdapterSpec inboundAdapter = RabbitStream.inboundAdapter(env); - ConsumerCustomizer customizer = mock(ConsumerCustomizer.class); - inboundAdapter.configureContainer(container -> container.consumerCustomizer(customizer)); - inboundAdapter.start(); - verify(customizer).accept(any(), any()); - } - - @Autowired - QueueChannel fromRabbitViaPublisher; - - @Test - void messageReceivedFromRabbitListenerViaPublisher() { - this.amqpTemplate.convertAndSend("publisherQueue", "test data"); - - Message receive = this.fromRabbitViaPublisher.receive(10_000); - assertThat(receive).isNotNull() - .extracting(Message::getPayload) - .isEqualTo("TEST DATA"); - } - - @Configuration - @EnableIntegration - @EnableRabbit - @EnablePublisher - public static class ContextConfiguration { - - @Bean - public ConnectionFactory rabbitConnectionFactory() { - return new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - } - - @Bean - public RabbitTemplate amqpTemplate() { - return new RabbitTemplate(rabbitConnectionFactory()); - } - - @Bean - public RabbitAdmin amqpAdmin() { - return new RabbitAdmin(rabbitConnectionFactory()); - } - - @Bean - public Queue queue() { - return new AnonymousQueue(); - } - - @Bean - public Queue queue2() { - return new AnonymousQueue(); - } - - @Bean - public Queue defaultReplyTo() { - return new Queue("defaultReplyTo"); - } - - @Bean - public IntegrationFlow amqpFlow(ConnectionFactory rabbitConnectionFactory, AmqpTemplate amqpTemplate) { - return IntegrationFlow - .from(Amqp.inboundGateway(rabbitConnectionFactory, amqpTemplate, queue()) - .id("amqpInboundGateway") - .configureContainer(c -> c - .id("amqpInboundGatewayContainer") - .recoveryInterval(5000) - .concurrentConsumers(1)) - .defaultReplyTo(defaultReplyTo().getName())) - .transform("hello "::concat) - .transform(String.class, String::toUpperCase) - .get(); - } - - // syntax only - public IntegrationFlow amqpDMLCFlow(ConnectionFactory rabbitConnectionFactory, AmqpTemplate amqpTemplate) { - return IntegrationFlow - .from(Amqp.inboundGateway(new DirectMessageListenerContainer()) - .id("amqpInboundGateway") - .configureContainer(c -> c - .recoveryInterval(5000) - .consumersPerQueue(1)) - .defaultReplyTo(defaultReplyTo().getName())) - .transform("hello "::concat) - .transform(String.class, String::toUpperCase) - .get(); - } - - @Bean - public IntegrationFlow amqpOutboundFlow(ConnectionFactory rabbitConnectionFactory, AmqpTemplate amqpTemplate) { - return IntegrationFlow.from(Amqp.channel("amqpOutboundInput", rabbitConnectionFactory)) - .handle(Amqp.outboundAdapter(amqpTemplate).routingKeyExpression("headers.routingKey")) - .get(); - } - - @Bean - public Queue fooQueue() { - return new Queue("si.dsl.test"); - } - - @Bean - public Queue amqpReplyChannel() { - return new Queue("amqpReplyChannel"); - } - - @Bean - public IntegrationFlow amqpInboundFlow(ConnectionFactory rabbitConnectionFactory) { - return IntegrationFlow.from(Amqp.inboundAdapter(rabbitConnectionFactory, fooQueue()) - .configureContainer(container -> container.consumerBatchEnabled(true) - .batchSize(2)) - .batchMode(BatchMode.EXTRACT_PAYLOADS) - .id("amqpInboundFlowAdapter")) - .transform(String.class, String::toUpperCase) - .channel(Amqp.pollableChannel(rabbitConnectionFactory) - .queueName("amqpReplyChannel") - .channelTransacted(true)) - .get(); - } - - @Bean - public AtomicReference lefe() { - return new AtomicReference<>(); - } - - @Bean - public AtomicReference raw() { - return new AtomicReference<>(); - } - - @Bean - public Queue exQueue() { - return new Queue("si.dsl.exception.test", true, false, false, - new StringObjectMapBuilder() - .put("x-dead-letter-exchange", "") - .put("x-dead-letter-routing-key", exDLQ().getName()) - .get()); - } - - @Bean - public Queue exDLQ() { - return new Queue("si.dsl.exception.test.dlq"); - } - - @Bean - public IntegrationFlow inboundWithExceptionFlow(ConnectionFactory cf) { - return IntegrationFlow.from(Amqp.inboundAdapter(cf, exQueue()) - .configureContainer(c -> c.defaultRequeueRejected(false)) - .errorChannel("errors.input")) - .handle(m -> { - throw new RuntimeException("fail"); - }) - .get(); - } - - @Bean - public IntegrationFlow errors() { - return f -> f.handle(m -> { - raw().set(m.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, - org.springframework.amqp.core.Message.class)); - if (m.getPayload() instanceof ListenerExecutionFailedException) { - lefe().set((ListenerExecutionFailedException) m.getPayload()); - } - throw (RuntimeException) m.getPayload(); - }); - } - - @Bean - public Queue exConvQueue() { - return new Queue("si.dsl.conv.exception.test", true, false, false, - new StringObjectMapBuilder() - .put("x-dead-letter-exchange", "") - .put("x-dead-letter-routing-key", exConvDLQ().getName()) - .get()); - } - - @Bean - public Queue exConvDLQ() { - return new Queue("si.dsl.conv.exception.test.dlq"); - } - - @Bean - public IntegrationFlow inboundWithConvExceptionFlow(ConnectionFactory cf) { - return IntegrationFlow.from(Amqp.inboundAdapter(cf, exConvQueue()) - .configureContainer(c -> c.defaultRequeueRejected(false)) - .messageConverter(new SimpleMessageConverter() { - - @Override - public Object fromMessage(org.springframework.amqp.core.Message message) - throws MessageConversionException { - throw new MessageConversionException("fail"); - } - - }) - .errorChannel("errors.input")) - .get(); - } - - @Bean - public Queue asyncReplies() { - return new Queue("asyncReplies"); - } - - @Bean - public AsyncRabbitTemplate asyncRabbitTemplate(ConnectionFactory rabbitConnectionFactory) { - return new AsyncRabbitTemplate(rabbitConnectionFactory, "", "", "asyncReplies"); - } - - @Bean - public IntegrationFlow amqpAsyncOutboundFlow(AsyncRabbitTemplate asyncRabbitTemplate) { - return f -> f - .handle(Amqp.asyncOutboundGateway(asyncRabbitTemplate) - .routingKeyFunction(m -> queue().getName()), - e -> e.id("asyncOutboundGateway")); - } - - @Bean - public AmqpPollableMessageChannelSpec unitChannel( - ConnectionFactory rabbitConnectionFactory) { - - return Amqp.pollableChannel(rabbitConnectionFactory) - .queueName("si.dsl.test") - .channelTransacted(true) - .extractPayload(true) - .inboundHeaderMapper(mapperIn()) - .outboundHeaderMapper(mapperOut()) - .defaultDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); - } - - @Bean - public AmqpHeaderMapper mapperIn() { - return DefaultAmqpHeaderMapper.inboundMapper(); - } - - @Bean - public AmqpHeaderMapper mapperOut() { - return DefaultAmqpHeaderMapper.outboundMapper(); - } - - @Bean - public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory( - ConnectionFactory rabbitConnectionFactory) { - - SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); - factory.setConnectionFactory(rabbitConnectionFactory); - return factory; - } - - @Bean - QueueChannel fromRabbitViaPublisher() { - return new QueueChannel(); - } - - @RabbitListener(queuesToDeclare = @org.springframework.amqp.rabbit.annotation.Queue("publisherQueue")) - @Publisher("fromRabbitViaPublisher") - @Payload("#args.payload.toUpperCase()") - public void consumeForPublisher(String payload) { - - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/dsl/RabbitStreamTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/dsl/RabbitStreamTests.java deleted file mode 100644 index ddd2540e576..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/dsl/RabbitStreamTests.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.dsl; - -import com.rabbitmq.stream.Environment; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.core.QueueBuilder; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.rabbit.stream.config.SuperStream; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 6.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class RabbitStreamTests implements RabbitTestContainer { - - @Autowired - MessageChannel sendToRabbitStreamChannel; - - @Autowired - PollableChannel results; - - @Test - void rabbitStreamWithSpringIntegrationChannelAdapters() { - var testData = "test data"; - this.sendToRabbitStreamChannel.send(new GenericMessage<>(testData)); - - Message receive = results.receive(10_000); - - assertThat(receive).isNotNull() - .extracting(Message::getPayload) - .isEqualTo(testData); - } - - @Autowired - MessageChannel sendToRabbitSuperStreamChannel; - - @Test - void superStreamWithSpringIntegrationChannelAdapters() { - var testData = "test super data"; - this.sendToRabbitSuperStreamChannel.send(new GenericMessage<>(testData)); - - Message receive = results.receive(10_000); - - assertThat(receive).isNotNull() - .extracting(Message::getPayload) - .isEqualTo(testData); - } - - @Configuration - @EnableIntegration - public static class ContextConfiguration { - - @Bean - ConnectionFactory rabbitConnectionFactory() { - return new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - } - - @Bean - RabbitTemplate rabbitTemplate(ConnectionFactory rabbitConnectionFactory) { - return new RabbitTemplate(rabbitConnectionFactory); - } - - @Bean - Environment rabbitStreamEnvironment() { - return Environment.builder() - .port(RabbitTestContainer.streamPort()) - .build(); - } - - @Bean(initMethod = "initialize") - RabbitAdmin rabbitAdmin(ConnectionFactory rabbitConnectionFactory) { - return new RabbitAdmin(rabbitConnectionFactory); - } - - @Bean - Queue stream() { - return QueueBuilder.durable("test.stream1") - .stream() - .build(); - } - - @Bean - @ServiceActivator(inputChannel = "sendToRabbitStreamChannel") - RabbitStreamMessageHandlerSpec rabbitStreamMessageHandler(Environment env, Queue stream) { - return RabbitStream.outboundStreamAdapter(env, stream.getName()).sync(true); - } - - @Bean - IntegrationFlow rabbitStreamConsumer(Environment env, Queue stream) { - return IntegrationFlow.from( - RabbitStream.inboundAdapter(env) - .streamName(stream.getName())) - .channel("results") - .get(); - } - - @Bean - QueueChannel results() { - return new QueueChannel(); - } - - @Bean - SuperStream superStream() { - return new SuperStream("test.superStream1", 3); - } - - @Bean - @ServiceActivator(inputChannel = "sendToRabbitSuperStreamChannel") - AmqpOutboundChannelAdapterSpec rabbitSuperStreamMessageHandler(RabbitTemplate rabbitTemplate) { - return Amqp.outboundAdapter(rabbitTemplate) - .exchangeName("test.superStream1") - .routingKey("1"); - } - - @Bean - IntegrationFlow superStreamConsumer(Environment env) { - return IntegrationFlow.from( - RabbitStream.inboundAdapter(env) - .superStream("test.superStream1", "mySuperConsumer")) - .channel("results") - .get(); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java deleted file mode 100644 index 14e97d3f10d..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.aopalliance.intercept.MethodInterceptor; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.core.QueueBuilder; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.acks.AcknowledgmentCallback; -import org.springframework.integration.acks.AcknowledgmentCallback.Status; -import org.springframework.integration.amqp.dsl.Amqp; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.annotation.InboundChannelAdapter; -import org.springframework.integration.annotation.Poller; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.Pollers; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.handler.annotation.Header; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * - * @since 5.0.1 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpMessageSourceIntegrationTests implements RabbitTestContainer { - - static final String DSL_QUEUE = "AmqpMessageSourceIntegrationTests"; - - static final String QUEUE_WITH_DLQ = "AmqpMessageSourceIntegrationTests.withDLQ"; - - static final String DLQ = QUEUE_WITH_DLQ + ".dlq"; - - static final String INTERCEPT_QUEUE = "AmqpMessageSourceIntegrationTests.channel"; - - static final String NOAUTOACK_QUEUE = "AmqpMessageSourceIntegrationTests.noAutoAck"; - - @Autowired - private Config config; - - @Autowired - private ConfigurableApplicationContext context; - - @BeforeAll - static void initQueues() throws IOException, InterruptedException { - List queueNames = List.of(DSL_QUEUE, DLQ, INTERCEPT_QUEUE, NOAUTOACK_QUEUE); - for (String queue : queueNames) { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + queue); - } - } - - @AfterAll - static void deleteQueues() throws IOException, InterruptedException { - List queueNames = List.of(DSL_QUEUE, DLQ, INTERCEPT_QUEUE, NOAUTOACK_QUEUE); - for (String queue : queueNames) { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + queue); - } - } - - @BeforeEach - public void before() { - RabbitAdmin admin = new RabbitAdmin(this.config.connectionFactory()); - Queue queue = QueueBuilder.nonDurable(QUEUE_WITH_DLQ) - .autoDelete() - .withArgument("x-dead-letter-exchange", "") - .withArgument("x-dead-letter-routing-key", DLQ) - .build(); - admin.declareQueue(queue); - this.context.start(); - } - - @AfterEach - public void after() { - this.context.stop(); - } - - @Test - public void testImplicitNackThenAck() throws Exception { - RabbitTemplate template = new RabbitTemplate(this.config.connectionFactory()); - template.convertAndSend(QUEUE_WITH_DLQ, "foo"); - template.convertAndSend(QUEUE_WITH_DLQ, "nackIt"); - template.convertAndSend(DSL_QUEUE, "bar"); - template.convertAndSend(INTERCEPT_QUEUE, "baz"); - template.convertAndSend(NOAUTOACK_QUEUE, "qux"); - assertThat(this.config.latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(this.config.received).isEqualTo("foo"); - Message dead = template.receive(DLQ, 10_000); - assertThat(dead).isNotNull(); - assertThat(this.config.fromDsl).isEqualTo("bar"); - assertThat(this.config.fromInterceptedSource).isEqualTo("BAZ"); - assertThat(template.receive(NOAUTOACK_QUEUE)).isNull(); - assertThat(this.config.requeueLatch.getCount()).isEqualTo(1L); - this.config.callback.acknowledge(Status.REQUEUE); - assertThat(this.config.requeueLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Configuration - @EnableIntegration - public static class Config { - - private final CountDownLatch latch = new CountDownLatch(5); - - private final CountDownLatch requeueLatch = new CountDownLatch(2); - - private volatile String received; - - private volatile Object fromDsl; - - private volatile Object fromInterceptedSource; - - private volatile AcknowledgmentCallback callback; - - @InboundChannelAdapter(channel = "in", poller = @Poller(fixedDelay = "100"), autoStartup = "false") - @Bean - public MessageSource source() { - return new AmqpMessageSource(connectionFactory(), QUEUE_WITH_DLQ); - } - - @ServiceActivator(inputChannel = "in") - public void in(String in) { - try { - if ("nackIt".equals(in)) { - throw new RuntimeException("testing auto nack"); - } - else { - this.received = in; - } - } - finally { - this.latch.countDown(); - } - } - - @InboundChannelAdapter(channel = "noAutoAck", poller = @Poller(fixedDelay = "100"), autoStartup = "false") - @Bean - public MessageSource noAutoAckSource() { - return new AmqpMessageSource(connectionFactory(), NOAUTOACK_QUEUE) { - - @Override - protected AbstractIntegrationMessageBuilder doReceive() { - AbstractIntegrationMessageBuilder builder = super.doReceive(); - if (builder != null) { - Config.this.requeueLatch.countDown(); - } - return builder; - } - - }; - } - - @ServiceActivator(inputChannel = "noAutoAck") - public void ack(@Header(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK) - AcknowledgmentCallback callback) { - callback.noAutoAck(); - this.callback = callback; - latch.countDown(); - } - - @Bean - public IntegrationFlow flow() { - return IntegrationFlow.from(Amqp.inboundPolledAdapter(connectionFactory(), DSL_QUEUE), - e -> e.poller(Pollers.fixedDelay(100)).autoStartup(false)) - .handle(p -> { - this.fromDsl = p.getPayload(); - this.latch.countDown(); - }) - .get(); - } - - @Bean - public IntegrationFlow messageSourceChannelFlow() { - return IntegrationFlow.from(interceptedSource(), - e -> e.poller(Pollers.fixedDelay(100)).autoStartup(false)) - .handle(p -> { - this.fromInterceptedSource = p.getPayload(); - this.latch.countDown(); - }) - .get(); - } - - @Bean - public MessageSource interceptedSource() { - return new AmqpMessageSource(connectionFactory(), INTERCEPT_QUEUE); - } - - @Bean - public static BeanPostProcessor upcase() { - return new BeanPostProcessor() { - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (beanName.equals("interceptedSource")) { - ProxyFactory pf = new ProxyFactory(bean); - pf.addAdvice((MethodInterceptor) invocation -> { - - if (invocation.getMethod().getName().equals("receive")) { - org.springframework.messaging.Message message = - (org.springframework.messaging.Message) invocation.proceed(); - if (message == null) { - return null; - } - return MessageBuilder.withPayload(((String) message.getPayload()).toUpperCase()) - .copyHeaders(message.getHeaders()) - .build(); - } - else { - return invocation.proceed(); - } - }); - return pf.getProxy(); - } - return bean; - } - - }; - } - - @Bean - public ConnectionFactory connectionFactory() { - return new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceTests.java deleted file mode 100644 index f8bc095fe5d..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceTests.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import java.util.List; -import java.util.concurrent.ExecutorService; - -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.GetResponse; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.batch.MessageBatch; -import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.acks.AcknowledgmentCallback.Status; -import org.springframework.integration.amqp.support.AmqpMessageHeaderErrorMessageStrategy; -import org.springframework.messaging.Message; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0.1 - * - */ -public class AmqpMessageSourceTests { - - @Test - public void testAck() throws Exception { - Channel channel = mock(Channel.class); - willReturn(true).given(channel).isOpen(); - Envelope envelope = new Envelope(123L, false, "ex", "rk"); - BasicProperties props = - new BasicProperties.Builder() - .contentType(MessageProperties.DEFAULT_CONTENT_TYPE) - .build(); - GetResponse getResponse = new GetResponse(envelope, props, "bar".getBytes(), 0); - willReturn(getResponse).given(channel).basicGet("foo", false); - Connection connection = mock(Connection.class); - willReturn(true).given(connection).isOpen(); - willReturn(channel).given(connection).createChannel(); - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - willReturn(connection).given(connectionFactory).newConnection((ExecutorService) isNull(), anyString()); - - CachingConnectionFactory ccf = new CachingConnectionFactory(connectionFactory); - AmqpMessageSource source = new AmqpMessageSource(ccf, "foo"); - source.setRawMessageHeader(true); - Message received = source.receive(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)) - .isInstanceOf(org.springframework.amqp.core.Message.class); - assertThat(received.getHeaders().get(IntegrationMessageHeaderAccessor.SOURCE_DATA)) - .isSameAs(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)); - assertThat(received.getHeaders().get(AmqpHeaders.CONSUMER_QUEUE)).isEqualTo("foo"); - // make sure channel is not cached - org.springframework.amqp.rabbit.connection.Connection conn = ccf.createConnection(); - Channel notCached = conn.createChannel(false); // should not have been "closed" - verify(connection, times(2)).createChannel(); - StaticMessageHeaderAccessor.getAcknowledgmentCallback(received) - .acknowledge(Status.ACCEPT); - verify(channel).basicAck(123L, false); - Channel cached = conn.createChannel(false); // should have been "closed" - verify(connection, times(2)).createChannel(); - notCached.close(); - cached.close(); - ccf.destroy(); - verify(channel, times(2)).close(); - verify(connection).close(30000); - } - - @Test - public void testNAck() throws Exception { - testNackOrRequeue(false); - } - - @Test - public void testRequeue() throws Exception { - testNackOrRequeue(false); - } - - private void testNackOrRequeue(boolean requeue) throws Exception { - Channel channel = mock(Channel.class); - willReturn(true).given(channel).isOpen(); - Envelope envelope = new Envelope(123L, false, "ex", "rk"); - BasicProperties props = - new BasicProperties.Builder() - .contentType(MessageProperties.DEFAULT_CONTENT_TYPE) - .build(); - GetResponse getResponse = new GetResponse(envelope, props, "bar".getBytes(), 0); - willReturn(getResponse).given(channel).basicGet("foo", false); - Connection connection = mock(Connection.class); - willReturn(true).given(connection).isOpen(); - willReturn(channel).given(connection).createChannel(); - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - willReturn(connection).given(connectionFactory).newConnection((ExecutorService) isNull(), anyString()); - - CachingConnectionFactory ccf = new CachingConnectionFactory(connectionFactory); - AmqpMessageSource source = new AmqpMessageSource(ccf, "foo"); - Message received = source.receive(); - verify(connection).createChannel(); - StaticMessageHeaderAccessor.getAcknowledgmentCallback(received) - .acknowledge(requeue ? Status.REQUEUE : Status.REJECT); - verify(channel).basicReject(123L, requeue); - verify(connection).createChannel(); - ccf.destroy(); - verify(channel).close(); - verify(connection).close(30000); - } - - @SuppressWarnings({"unchecked"}) - @Test - public void testBatch() throws Exception { - SimpleBatchingStrategy bs = new SimpleBatchingStrategy(2, 10_000, 10_000L); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - org.springframework.amqp.core.Message message = - new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties); - bs.addToBatch("foo", "bar", message); - message = new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties); - MessageBatch batched = bs.addToBatch("foo", "bar", message); - - Channel channel = mock(Channel.class); - willReturn(true).given(channel).isOpen(); - Envelope envelope = new Envelope(123L, false, "ex", "rk"); - BasicProperties props = new BasicProperties.Builder() - .headers(batched.message().getMessageProperties().getHeaders()) - .contentType("text/plain") - .build(); - GetResponse getResponse = new GetResponse(envelope, props, batched.message().getBody(), 0); - willReturn(getResponse).given(channel).basicGet("foo", false); - Connection connection = mock(Connection.class); - willReturn(true).given(connection).isOpen(); - willReturn(channel).given(connection).createChannel(); - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - willReturn(connection).given(connectionFactory).newConnection((ExecutorService) isNull(), anyString()); - - CachingConnectionFactory ccf = new CachingConnectionFactory(connectionFactory); - AmqpMessageSource source = new AmqpMessageSource(ccf, "foo"); - Message received = source.receive(); - assertThat(received).isNotNull(); - assertThat(((List) received.getPayload())).contains("test1", "test2"); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/InboundEndpointTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/InboundEndpointTests.java deleted file mode 100644 index 08e1feefb28..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/InboundEndpointTests.java +++ /dev/null @@ -1,810 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import com.rabbitmq.client.Channel; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.batch.MessageBatch; -import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy; -import org.springframework.amqp.rabbit.connection.Connection; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareBatchMessageListener; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; -import org.springframework.amqp.rabbit.retry.MessageBatchRecoverer; -import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConversionException; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.retry.RetryPolicy; -import org.springframework.core.retry.RetryTemplate; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter.BatchMode; -import org.springframework.integration.amqp.support.AmqpMessageHeaderErrorMessageStrategy; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.amqp.support.ManualAckListenerExecutionFailedException; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer; -import org.springframework.integration.json.JsonToObjectTransformer; -import org.springframework.integration.json.ObjectToJsonTransformer; -import org.springframework.integration.mapping.support.JsonHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.integration.transformer.MessageTransformingHandler; -import org.springframework.integration.transformer.Transformer; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; - -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.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -/** - * @author Artem Bilan - * @author Gary Russell - * - * @since 3.0 - */ -public class InboundEndpointTests implements TestApplicationContextAware { - - @Test - public void testInt2809JavaTypePropertiesToAmqp() throws Exception { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - adapter.setMessageConverter(new JacksonJsonMessageConverter()); - - PollableChannel channel = new QueueChannel(); - - adapter.setOutputChannel(channel); - adapter.setBeanFactory(mock()); - adapter.setBindSourceMessage(true); - adapter.afterPropertiesSet(); - - Object payload = new Foo("bar1"); - - Transformer objectToJsonTransformer = new ObjectToJsonTransformer(); - Message jsonMessage = objectToJsonTransformer.transform(new GenericMessage<>(payload)); - - MessageProperties amqpMessageProperties = new MessageProperties(); - amqpMessageProperties.setDeliveryTag(123L); - org.springframework.amqp.core.Message amqpMessage = - new SimpleMessageConverter().toMessage(jsonMessage.getPayload(), amqpMessageProperties); - DefaultAmqpHeaderMapper.inboundMapper().fromHeadersToRequest(jsonMessage.getHeaders(), amqpMessageProperties); - - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - Channel rabbitChannel = mock(Channel.class); - listener.onMessage(amqpMessage, rabbitChannel); - - Message result = channel.receive(1000); - assertThat(result.getPayload()).isEqualTo(payload); - - assertThat(result.getHeaders().get(AmqpHeaders.CHANNEL)).isSameAs(rabbitChannel); - assertThat(result.getHeaders().get(AmqpHeaders.DELIVERY_TAG)).isEqualTo(123L); - org.springframework.amqp.core.Message sourceData = StaticMessageHeaderAccessor.getSourceData(result); - assertThat(sourceData).isSameAs(amqpMessage); - } - - @Test - public void testInt2809JavaTypePropertiesFromAmqp() throws Exception { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - - PollableChannel channel = new QueueChannel(); - - adapter.setOutputChannel(channel); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - - Object payload = new Foo("bar1"); - - MessageProperties amqpMessageProperties = new MessageProperties(); - org.springframework.amqp.core.Message amqpMessage = - new JacksonJsonMessageConverter().toMessage(payload, amqpMessageProperties); - - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - listener.onMessage(amqpMessage, null); - - Message receive = channel.receive(1000); - - Message result = new JsonToObjectTransformer().transform(receive); - - assertThat(result.getPayload()).isEqualTo(payload); - org.springframework.amqp.core.Message sourceData = StaticMessageHeaderAccessor.getSourceData(result); - assertThat(sourceData).isNull(); - } - - @Test - public void testMessageConverterJsonHeadersHavePrecedenceOverMessageHeaders() throws Exception { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - - DirectChannel channel = new DirectChannel(); - - final Channel rabbitChannel = mock(Channel.class); - - channel.subscribe(new MessageTransformingHandler(message -> { - assertThat(message.getHeaders().get(AmqpHeaders.CHANNEL)).isSameAs(rabbitChannel); - assertThat(message.getHeaders().get(AmqpHeaders.DELIVERY_TAG)).isEqualTo(123L); - return MessageBuilder.fromMessage(message) - .setHeader(JsonHeaders.TYPE_ID, "foo") - .setHeader(JsonHeaders.CONTENT_TYPE_ID, "bar") - .setHeader(JsonHeaders.KEY_TYPE_ID, "baz") - .build(); - })); - - RabbitTemplate rabbitTemplate = spy(new RabbitTemplate()); - rabbitTemplate.setMessageConverter(new JacksonJsonMessageConverter()); - - CountDownLatch sendLatch = new CountDownLatch(1); - - Mockito.doAnswer(invocation -> { - org.springframework.amqp.core.Message message = - invocation.getArgument(2); - Map headers = message.getMessageProperties().getHeaders(); - assertThat(headers.containsKey(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))).isTrue(); - assertThat(headers.get(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))).isNotEqualTo("foo"); - assertThat(headers.containsKey(JsonHeaders.CONTENT_TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))).isFalse(); - assertThat(headers.containsKey(JsonHeaders.KEY_TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))).isFalse(); - assertThat(headers.containsKey(JsonHeaders.TYPE_ID)).isFalse(); - assertThat(headers.containsKey(JsonHeaders.KEY_TYPE_ID)).isFalse(); - assertThat(headers.containsKey(JsonHeaders.CONTENT_TYPE_ID)).isFalse(); - sendLatch.countDown(); - return null; - }).when(rabbitTemplate) - .send(anyString(), anyString(), any(org.springframework.amqp.core.Message.class), isNull()); - - AmqpInboundGateway gateway = new AmqpInboundGateway(container, rabbitTemplate); - gateway.setMessageConverter(new JacksonJsonMessageConverter()); - gateway.setRequestChannel(channel); - gateway.setBeanFactory(mock(BeanFactory.class)); - gateway.setDefaultReplyTo("foo"); - gateway.setReplyHeadersMappedLast(true); - gateway.afterPropertiesSet(); - - Object payload = new Foo("bar1"); - - MessageProperties amqpMessageProperties = new MessageProperties(); - amqpMessageProperties.setDeliveryTag(123L); - org.springframework.amqp.core.Message amqpMessage = - new JacksonJsonMessageConverter().toMessage(payload, amqpMessageProperties); - - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - listener.onMessage(amqpMessage, rabbitChannel); - - assertThat(sendLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - public void testAdapterConversionError() throws Exception { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - QueueChannel outputChannel = new QueueChannel(); - adapter.setOutputChannel(outputChannel); - QueueChannel errorChannel = new QueueChannel(); - adapter.setErrorChannel(errorChannel); - adapter.setMessageConverter(new SimpleMessageConverter() { - - @Override - public Object fromMessage(org.springframework.amqp.core.Message message) throws MessageConversionException { - throw new MessageConversionException("intended"); - } - - }); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - org.springframework.amqp.core.Message message = mock(org.springframework.amqp.core.Message.class); - MessageProperties props = new MessageProperties(); - props.setDeliveryTag(42L); - given(message.getMessageProperties()).willReturn(props); - ((ChannelAwareMessageListener) container.getMessageListener()) - .onMessage(message, null); - assertThat(outputChannel.receive(0)).isNull(); - Message received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload().getClass()).isEqualTo(ListenerExecutionFailedException.class); - - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - adapter.afterPropertiesSet(); // ack mode is now captured during init - Channel channel = mock(Channel.class); - ((ChannelAwareMessageListener) container.getMessageListener()) - .onMessage(message, channel); - assertThat(outputChannel.receive(0)).isNull(); - received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload()).isInstanceOf(ManualAckListenerExecutionFailedException.class); - ManualAckListenerExecutionFailedException ex = (ManualAckListenerExecutionFailedException) received - .getPayload(); - assertThat(ex.getChannel()).isEqualTo(channel); - assertThat(ex.getDeliveryTag()).isEqualTo(props.getDeliveryTag()); - } - - @Test - public void testGatewayConversionError() throws Exception { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - AmqpInboundGateway adapter = new AmqpInboundGateway(container); - QueueChannel outputChannel = new QueueChannel(); - adapter.setRequestChannel(outputChannel); - QueueChannel errorChannel = new QueueChannel(); - adapter.setErrorChannel(errorChannel); - adapter.setMessageConverter(new MessageConverter() { - - @Override - public org.springframework.amqp.core.Message toMessage(Object object, MessageProperties messageProperties) - throws MessageConversionException { - throw new MessageConversionException("intended"); - } - - @Override - public Object fromMessage(org.springframework.amqp.core.Message message) throws MessageConversionException { - throw new MessageConversionException("intended"); - } - - }); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - org.springframework.amqp.core.Message message = mock(org.springframework.amqp.core.Message.class); - MessageProperties props = new MessageProperties(); - props.setDeliveryTag(42L); - given(message.getMessageProperties()).willReturn(props); - ((ChannelAwareMessageListener) container.getMessageListener()) - .onMessage(message, null); - assertThat(outputChannel.receive(0)).isNull(); - Message received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - Channel channel = mock(Channel.class); - ((ChannelAwareMessageListener) container.getMessageListener()) - .onMessage(message, channel); - assertThat(outputChannel.receive(0)).isNull(); - received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload()).isInstanceOf(ManualAckListenerExecutionFailedException.class); - ManualAckListenerExecutionFailedException ex = (ManualAckListenerExecutionFailedException) received - .getPayload(); - assertThat(ex.getChannel()).isEqualTo(channel); - assertThat(ex.getDeliveryTag()).isEqualTo(props.getDeliveryTag()); - } - - @Test - public void testRetryWithinOnMessageAdapter() throws Exception { - ConnectionFactory connectionFactory = mock(); - AbstractMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - adapter.setOutputChannel(new DirectChannel()); - adapter.setRetryTemplate(new RetryTemplate(RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build())); - QueueChannel errors = new QueueChannel(); - ErrorMessageSendingRecoverer recoveryCallback = new ErrorMessageSendingRecoverer(errors); - recoveryCallback.setErrorMessageStrategy(new AmqpMessageHeaderErrorMessageStrategy()); - adapter.setRecoveryCallback(recoveryCallback); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - listener.onMessage(org.springframework.amqp.core.MessageBuilder.withBody("foo".getBytes()) - .andProperties(new MessageProperties()).build(), null); - Message errorMessage = errors.receive(0); - assertThat(errorMessage).isNotNull(); - assertThat(errorMessage.getPayload()).isInstanceOf(MessagingException.class); - MessagingException payload = (MessagingException) errorMessage.getPayload(); - assertThat(payload.getMessage()).contains("Dispatcher has no"); - assertThat(StaticMessageHeaderAccessor.getDeliveryAttempt(payload.getFailedMessage()).get()).isEqualTo(3); - org.springframework.amqp.core.Message amqpMessage = errorMessage.getHeaders() - .get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, - org.springframework.amqp.core.Message.class); - assertThat(amqpMessage).isNotNull(); - assertThat(errors.receive(0)).isNull(); - } - - @Test - public void testRetryWithMessageRecovererOnMessageAdapter() throws Exception { - ConnectionFactory connectionFactory = mock(); - AbstractMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - adapter.setOutputChannel(new DirectChannel()); - adapter.setRetryTemplate(new RetryTemplate(RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build())); - AtomicReference recoveredMessage = new AtomicReference<>(); - AtomicReference recoveredError = new AtomicReference<>(); - CountDownLatch recoveredLatch = new CountDownLatch(1); - adapter.setMessageRecoverer((message, cause) -> { - recoveredMessage.set(message); - recoveredError.set(cause); - recoveredLatch.countDown(); - }); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - org.springframework.amqp.core.Message amqpMessage = - org.springframework.amqp.core.MessageBuilder.withBody("foo".getBytes()) - .andProperties(new MessageProperties()) - .build(); - listener.onMessage(amqpMessage, null); - - assertThat(recoveredLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - assertThat(recoveredError.get()) - .isInstanceOf(MessagingException.class) - .extracting(Throwable::getMessage, InstanceOfAssertFactories.STRING) - .contains("Dispatcher has no"); - - assertThat(recoveredMessage.get()).isSameAs(amqpMessage); - } - - @Test - public void testRetryWithinOnMessageGateway() throws Exception { - ConnectionFactory connectionFactory = mock(); - AbstractMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - AmqpInboundGateway adapter = new AmqpInboundGateway(container); - adapter.setRequestChannel(new DirectChannel()); - adapter.setRetryTemplate(new RetryTemplate(RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build())); - QueueChannel errors = new QueueChannel(); - ErrorMessageSendingRecoverer recoveryCallback = new ErrorMessageSendingRecoverer(errors); - recoveryCallback.setErrorMessageStrategy(new AmqpMessageHeaderErrorMessageStrategy()); - adapter.setRecoveryCallback(recoveryCallback); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - listener.onMessage(org.springframework.amqp.core.MessageBuilder.withBody("foo".getBytes()) - .andProperties(new MessageProperties()).build(), null); - Message errorMessage = errors.receive(0); - assertThat(errorMessage).isNotNull(); - assertThat(errorMessage.getPayload()).isInstanceOf(MessagingException.class); - MessagingException payload = (MessagingException) errorMessage.getPayload(); - assertThat(payload.getMessage()).contains("Dispatcher has no"); - assertThat(StaticMessageHeaderAccessor.getDeliveryAttempt(payload.getFailedMessage()).get()).isEqualTo(3); - org.springframework.amqp.core.Message amqpMessage = errorMessage.getHeaders() - .get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, - org.springframework.amqp.core.Message.class); - assertThat(amqpMessage).isNotNull(); - assertThat(errors.receive(0)).isNull(); - } - - @Test - public void testRetryWithMessageRecovererOnMessageGateway() throws Exception { - ConnectionFactory connectionFactory = mock(); - AbstractMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - AmqpInboundGateway adapter = new AmqpInboundGateway(container); - adapter.setRequestChannel(new DirectChannel()); - adapter.setRetryTemplate(new RetryTemplate(RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build())); - AtomicReference recoveredMessage = new AtomicReference<>(); - AtomicReference recoveredError = new AtomicReference<>(); - CountDownLatch recoveredLatch = new CountDownLatch(1); - adapter.setMessageRecoverer((message, cause) -> { - recoveredMessage.set(message); - recoveredError.set(cause); - recoveredLatch.countDown(); - }); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - org.springframework.amqp.core.Message amqpMessage = - org.springframework.amqp.core.MessageBuilder.withBody("foo".getBytes()) - .andProperties(new MessageProperties()) - .build(); - listener.onMessage(amqpMessage, null); - - assertThat(recoveredLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - assertThat(recoveredError.get()) - .isInstanceOf(MessagingException.class) - .extracting(Throwable::getMessage, InstanceOfAssertFactories.STRING) - .contains("Dispatcher has no"); - - assertThat(recoveredMessage.get()).isSameAs(amqpMessage); - } - - @SuppressWarnings({"unchecked"}) - @Test - public void testBatchAdapter() throws Exception { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(mock()); - container.setDeBatchingEnabled(false); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - QueueChannel out = new QueueChannel(); - adapter.setOutputChannel(out); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - SimpleBatchingStrategy bs = new SimpleBatchingStrategy(2, 10_000, 10_000L); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - org.springframework.amqp.core.Message message = - new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties); - bs.addToBatch("foo", "bar", message); - message = new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties); - MessageBatch batched = bs.addToBatch("foo", "bar", message); - listener.onMessage(batched.message(), null); - Message received = out.receive(0); - assertThat(received).isNotNull(); - assertThat(((List) received.getPayload())).contains("test1", "test2"); - } - - @SuppressWarnings({"unchecked"}) - @Test - public void testBatchGateway() throws Exception { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(mock()); - container.setDeBatchingEnabled(false); - AmqpInboundGateway gateway = new AmqpInboundGateway(container); - QueueChannel out = new QueueChannel(); - gateway.setRequestChannel(out); - gateway.setBindSourceMessage(true); - gateway.setReplyTimeout(0); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener(); - SimpleBatchingStrategy bs = new SimpleBatchingStrategy(2, 10_000, 10_000L); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - org.springframework.amqp.core.Message message = - new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties); - bs.addToBatch("foo", "bar", message); - message = new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties); - MessageBatch batched = bs.addToBatch("foo", "bar", message); - listener.onMessage(batched.message(), null); - Message received = out.receive(0); - assertThat(received).isNotNull(); - assertThat(((List) received.getPayload())).contains("test1", "test2"); - org.springframework.amqp.core.Message sourceData = StaticMessageHeaderAccessor.getSourceData(received); - assertThat(sourceData).isSameAs(batched.message()); - } - - @SuppressWarnings({"unchecked"}) - @Test - public void testConsumerBatchExtract() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(mock()); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - QueueChannel out = new QueueChannel(); - adapter.setOutputChannel(out); - adapter.setBatchMode(BatchMode.EXTRACT_PAYLOADS_WITH_HEADERS); - adapter.setHeaderNameForBatchedHeaders("some_batch_headers"); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareBatchMessageListener listener = (ChannelAwareBatchMessageListener) container.getMessageListener(); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - List messages = new ArrayList<>(); - messages.add(new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties)); - messages.add(new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties)); - listener.onMessageBatch(messages, null); - Message received = out.receive(0); - assertThat(received).isNotNull(); - assertThat(((List) received.getPayload())).contains("test1", "test2"); - assertThat(received.getHeaders().get("some_batch_headers", List.class)) - .hasSize(2); - } - - @SuppressWarnings({"unchecked"}) - @Test - public void testConsumerBatch() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(mock()); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - QueueChannel out = new QueueChannel(); - adapter.setOutputChannel(out); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareBatchMessageListener listener = (ChannelAwareBatchMessageListener) container.getMessageListener(); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - List messages = new ArrayList<>(); - messages.add(new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties)); - messages.add(new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties)); - listener.onMessageBatch(messages, null); - Message received = out.receive(0); - assertThat(received).isNotNull(); - assertThat(((List>) received.getPayload())) - .extracting(message -> message.getPayload()) - .contains("test1", "test2"); - } - - @Test - public void testConsumerBatchAndWrongMessageRecoverer() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(mock()); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.setRetryTemplate(new RetryTemplate()); - adapter.setMessageRecoverer((message, cause) -> { - }); - assertThatIllegalArgumentException() - .isThrownBy(adapter::afterPropertiesSet) - .withMessageStartingWith("The 'messageRecoverer' must be an instance of MessageBatchRecoverer " + - "when consumer configured for batch mode"); - } - - @Test - public void testExclusiveRecover() { - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(mock()); - adapter.setRetryTemplate(new RetryTemplate()); - adapter.setMessageRecoverer((message, cause) -> { - }); - adapter.setRecoveryCallback((context, cause) -> null); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - assertThatIllegalStateException() - .isThrownBy(adapter::afterPropertiesSet) - .withMessageStartingWith("Only one of 'recoveryCallback' or 'messageRecoverer' may be provided, " + - "but not both"); - } - - @Test - public void testAdapterConversionErrorConsumerBatchExtract() { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - QueueChannel outputChannel = new QueueChannel(); - adapter.setOutputChannel(outputChannel); - QueueChannel errorChannel = new QueueChannel(); - adapter.setErrorChannel(errorChannel); - adapter.setMessageConverter(new SimpleMessageConverter() { - - @Override - public Object fromMessage(org.springframework.amqp.core.Message message) throws MessageConversionException { - throw new MessageConversionException("intended"); - } - - }); - adapter.setBatchMode(BatchMode.EXTRACT_PAYLOADS); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(42L); - List messages = new ArrayList<>(); - messages.add(new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties)); - messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(43L); - messages.add(new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties)); - ((ChannelAwareBatchMessageListener) container.getMessageListener()) - .onMessageBatch(messages, null); - assertThat(outputChannel.receive(0)).isNull(); - Message received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload().getClass()).isEqualTo(ListenerExecutionFailedException.class); - - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - adapter.afterPropertiesSet(); // ack mode is now captured during init - Channel channel = mock(Channel.class); - ((ChannelAwareBatchMessageListener) container.getMessageListener()) - .onMessageBatch(messages, channel); - assertThat(outputChannel.receive(0)).isNull(); - received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload()).isInstanceOf(ManualAckListenerExecutionFailedException.class); - ManualAckListenerExecutionFailedException ex = (ManualAckListenerExecutionFailedException) received - .getPayload(); - assertThat(ex.getChannel()).isEqualTo(channel); - assertThat(ex.getDeliveryTag()).isEqualTo(43L); - } - - @Test - public void testAdapterConversionErrorConsumerBatch() { - Connection connection = mock(); - willReturn(mock(Channel.class)).given(connection).createChannel(anyBoolean()); - ConnectionFactory connectionFactory = mock(); - when(connectionFactory.createConnection()).thenReturn(connection); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - QueueChannel outputChannel = new QueueChannel(); - adapter.setOutputChannel(outputChannel); - QueueChannel errorChannel = new QueueChannel(); - adapter.setErrorChannel(errorChannel); - adapter.setMessageConverter(new SimpleMessageConverter() { - - @Override - public Object fromMessage(org.springframework.amqp.core.Message message) throws MessageConversionException { - throw new MessageConversionException("intended"); - } - - }); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(42L); - List messages = new ArrayList<>(); - messages.add(new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties)); - messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(43L); - messages.add(new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties)); - ((ChannelAwareBatchMessageListener) container.getMessageListener()) - .onMessageBatch(messages, null); - assertThat(outputChannel.receive(0)).isNull(); - Message received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload().getClass()).isEqualTo(ListenerExecutionFailedException.class); - - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - adapter.afterPropertiesSet(); // ack mode is now captured during init - Channel channel = mock(Channel.class); - ((ChannelAwareBatchMessageListener) container.getMessageListener()) - .onMessageBatch(messages, channel); - assertThat(outputChannel.receive(0)).isNull(); - received = errorChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getHeaders().get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE)).isNotNull(); - assertThat(received.getPayload()).isInstanceOf(ManualAckListenerExecutionFailedException.class); - ManualAckListenerExecutionFailedException ex = (ManualAckListenerExecutionFailedException) received - .getPayload(); - assertThat(ex.getChannel()).isEqualTo(channel); - assertThat(ex.getDeliveryTag()).isEqualTo(43L); - } - - @Test - public void testRetryWithinOnMessageAdapterConsumerBatch() { - ConnectionFactory connectionFactory = mock(); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - adapter.setOutputChannel(new DirectChannel()); - adapter.setRetryTemplate(new RetryTemplate(RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build())); - QueueChannel errors = new QueueChannel(); - ErrorMessageSendingRecoverer recoveryCallback = new ErrorMessageSendingRecoverer(errors); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - recoveryCallback.setErrorMessageStrategy(new AmqpMessageHeaderErrorMessageStrategy()); - adapter.setRecoveryCallback(recoveryCallback); - adapter.afterPropertiesSet(); - ChannelAwareBatchMessageListener listener = (ChannelAwareBatchMessageListener) container.getMessageListener(); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(42L); - List messages = new ArrayList<>(); - messages.add(new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties)); - messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(43L); - messages.add(new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties)); - listener.onMessageBatch(messages, null); - Message errorMessage = errors.receive(0); - assertThat(errorMessage).isNotNull(); - assertThat(errorMessage.getPayload()).isInstanceOf(MessagingException.class); - MessagingException payload = (MessagingException) errorMessage.getPayload(); - assertThat(payload.getMessage()).contains("Dispatcher has no"); - assertThat(StaticMessageHeaderAccessor.getDeliveryAttempt(payload.getFailedMessage()).get()).isEqualTo(3); - @SuppressWarnings("unchecked") - List amqpMessages = errorMessage.getHeaders() - .get(AmqpMessageHeaderErrorMessageStrategy.AMQP_RAW_MESSAGE, - List.class); - assertThat(amqpMessages).isNotNull(); - assertThat(amqpMessages).hasSize(2); - @SuppressWarnings("unchecked") - List> msgs = (List>) payload.getFailedMessage().getPayload(); - assertThat(msgs).hasSize(2); - assertThat(msgs).extracting(msg -> StaticMessageHeaderAccessor.getDeliveryAttempt(msg).get()) - .contains(3, 3); - assertThat(msgs).extracting(msg -> msg.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class)) - .contains(42L, 43L); - assertThat(errors.receive(0)).isNull(); - } - - @Test - public void testRetryWithMessageRecovererOnMessageAdapterConsumerBatch() throws InterruptedException { - ConnectionFactory connectionFactory = mock(); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); - container.setConsumerBatchEnabled(true); - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container); - adapter.setOutputChannel(new DirectChannel()); - adapter.setRetryTemplate(new RetryTemplate(RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build())); - AtomicReference> recoveredMessages = new AtomicReference<>(); - AtomicReference recoveredError = new AtomicReference<>(); - CountDownLatch recoveredLatch = new CountDownLatch(1); - adapter.setMessageRecoverer((MessageBatchRecoverer) (messages, cause) -> { - recoveredMessages.set(messages); - recoveredError.set(cause); - recoveredLatch.countDown(); - }); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - ChannelAwareBatchMessageListener listener = (ChannelAwareBatchMessageListener) container.getMessageListener(); - MessageProperties messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(42L); - List messages = new ArrayList<>(); - messages.add(new org.springframework.amqp.core.Message("test1".getBytes(), messageProperties)); - messageProperties = new MessageProperties(); - messageProperties.setContentType("text/plain"); - messageProperties.setDeliveryTag(43L); - messages.add(new org.springframework.amqp.core.Message("test2".getBytes(), messageProperties)); - listener.onMessageBatch(messages, null); - - assertThat(recoveredLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - assertThat(recoveredError.get()) - .isInstanceOf(MessagingException.class) - .extracting(Throwable::getMessage, InstanceOfAssertFactories.STRING) - .contains("Dispatcher has no"); - - assertThat(recoveredMessages.get()).isSameAs(messages); - } - - public record Foo(String bar) { - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/ManualAckTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/ManualAckTests.java deleted file mode 100644 index 1d330c006b0..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/ManualAckTests.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.inbound; - -import com.rabbitmq.client.Channel; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.AnonymousQueue; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.annotation.MessageEndpoint; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.handler.annotation.Header; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * @author Gary Russell - * @since 4.0 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class ManualAckTests implements RabbitTestContainer { - - @Autowired - private MessageChannel foo; - - @Autowired - private PollableChannel bar; - - @Autowired - private SimpleMessageListenerContainer container; - - @Autowired - private RabbitTemplate template; - - @Test - public void testManual() { - AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(this.container); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.setOutputChannel(foo); - adapter.afterPropertiesSet(); - adapter.start(); - this.template.convertAndSend("Hello, world"); - Message out = bar.receive(5000); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo(1); - out = bar.receive(5000); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo(2); - out = bar.receive(5000); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo(3); - out = bar.receive(1000); - assertThat(out).isNull(); - adapter.stop(); - } - - @Configuration - @EnableIntegration - @ComponentScan - @MessageEndpoint - public static class ManualAckConfig { - - private int called; - - @ServiceActivator(inputChannel = "foo", outputChannel = "bar") - public Integer handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Channel channel, - @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws Exception { - if (++called > 2) { - channel.basicAck(deliveryTag, false); - } - else { - channel.basicNack(deliveryTag, false, true); - } - return called; - } - - @Bean - public QueueChannel bar() { - return new QueueChannel(); - } - - @Bean - public CachingConnectionFactory connectionFactory() { - CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); - connectionFactory.setPort(RabbitTestContainer.amqpPort()); - return connectionFactory; - } - - @Bean - public Queue queue() { - return new AnonymousQueue(); - } - - @Bean - public SimpleMessageListenerContainer container() { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory()); - container.setQueues(queue()); - container.setAcknowledgeMode(AcknowledgeMode.MANUAL); - container.setAutoStartup(false); - return container; - } - - @Bean - public RabbitTemplate template() { - RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); - rabbitTemplate.setRoutingKey(queue().getName()); - return rabbitTemplate; - } - - @Bean - public RabbitAdmin admin() { - return new RabbitAdmin(connectionFactory()); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml deleted file mode 100644 index ec05093782c..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java deleted file mode 100644 index fc6d583aa1a..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.connection.CorrelationData.Confirm; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.utils.test.TestUtils; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.integration.amqp.support.NackedAmqpMessageException; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.amqp.support.ReturnedAmqpMessageException; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.mapping.support.JsonHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.willDoNothing; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Gunnar Hillert - * - * @since 2.1 - * - */ -@SpringJUnitConfig -@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) -public class AmqpOutboundEndpointTests implements RabbitTestContainer { - - @Autowired - BeanFactory beanFactory; - - @Autowired - private MessageChannel pcRequestChannel; - - @Autowired - private MessageChannel pcMessageCorrelationRequestChannel; - - @Autowired - private MessageChannel multiSendChannel; - - @Autowired - private RabbitTemplate amqpTemplateConfirms; - - @Autowired - private Queue queue; - - @Autowired - private PollableChannel ackChannel; - - @Autowired - private MessageChannel pcRequestChannelForAdapter; - - @Autowired - private MessageChannel returnRequestChannel; - - @Autowired - private PollableChannel returnChannel; - - @Autowired - private MessageChannel ctRequestChannel; - - @Autowired - private ConnectionFactory connectionFactory; - - @Autowired - @Qualifier("withReturns.handler") - private AmqpOutboundEndpoint withReturns; - - @Test - public void testGatewayPublisherConfirms() throws Exception { - while (this.amqpTemplateConfirms.receive(this.queue.getName()) != null) { - // drain - } - - Message message = MessageBuilder.withPayload("hello") - .setHeader("amqp_confirmCorrelationData", "foo") - .setHeader(AmqpHeaders.CONTENT_TYPE, "application/json") - .build(); - this.pcRequestChannel.send(message); - Message ack = this.ackChannel.receive(10000); - assertThat(ack).isNotNull(); - assertThat(ack.getPayload()).isEqualTo("foo"); - assertThat(ack.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM)).isEqualTo(Boolean.TRUE); - - org.springframework.amqp.core.Message received = this.amqpTemplateConfirms.receive(this.queue.getName()); - assertThat(new String(received.getBody(), "UTF-8")).isEqualTo("\"hello\""); - assertThat(received.getMessageProperties().getContentType()).isEqualTo("application/json"); - assertThat(received.getMessageProperties().getHeaders() - .get(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))).isEqualTo("java.lang.String"); - - // test whole message is correlation - message = MessageBuilder.withPayload("hello") - .build(); - this.pcMessageCorrelationRequestChannel.send(message); - ack = ackChannel.receive(10000); - assertThat(ack).isNotNull(); - assertThat(ack.getPayload()).isSameAs(message.getPayload()); - assertThat(ack.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM)).isEqualTo(Boolean.TRUE); - - while (this.amqpTemplateConfirms.receive(this.queue.getName()) != null) { - // drain - } - } - - @Test - public void adapterWithPublisherConfirms() { - Message message = MessageBuilder.withPayload("hello") - .setHeader("amqp_confirmCorrelationData", "foo") - .build(); - this.pcRequestChannelForAdapter.send(message); - Message ack = this.ackChannel.receive(10000); - assertThat(ack).isNotNull(); - assertThat(ack.getPayload()).isEqualTo("foo"); - assertThat(ack.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM)).isEqualTo(Boolean.TRUE); - } - - @Test - public void multiSend() throws Exception { - RabbitTemplate template = new RabbitTemplate(this.connectionFactory); - template.setDefaultReceiveQueue(this.queue.getName()); - while (template.receive() != null) { - // drain - } - Collection> messages = new ArrayList<>(); - messages.add(new GenericMessage<>("foo")); - messages.add(new GenericMessage<>("bar")); - Message message = MessageBuilder.withPayload(messages) - .build(); - this.multiSendChannel.send(message); - org.springframework.amqp.core.Message m = receive(template); - assertThat(m).isNotNull(); - assertThat(new String(m.getBody(), StandardCharsets.UTF_8)).isEqualTo("foo"); - m = receive(template); - assertThat(m).isNotNull(); - assertThat(new String(m.getBody(), StandardCharsets.UTF_8)).isEqualTo("bar"); - } - - @Test - public void syncConfirmTimeout() { - Message message = new GenericMessage<>("foo"); - RabbitTemplate template = spy(RabbitTemplate.class); - willDoNothing().given(template).send(isNull(), isNull(), any(), any()); - List correlationList = new ArrayList<>(); - willReturn(correlationList).given(template).getUnconfirmed(100L); - willReturn(mock(ConnectionFactory.class)).given(template).getConnectionFactory(); - ArgumentCaptor correlationCaptor = ArgumentCaptor.forClass(CorrelationData.class); - AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(template); - PollableChannel nacks = new QueueChannel(); - endpoint.setConfirmNackChannel(nacks); - endpoint.setConfirmCorrelationExpressionString("headers.id"); - endpoint.setConfirmTimeout(100); - endpoint.setBeanFactory(beanFactory); - endpoint.afterPropertiesSet(); - endpoint.start(); - endpoint.handleMessage(message); - verify(template).send(isNull(), isNull(), any(), correlationCaptor.capture()); - CorrelationData correlation = correlationCaptor.getValue(); - correlationList.add(correlation); - assertThat(TestUtils.getPropertyValue(correlation, "message", Message.class)).isSameAs(message); - Message nack = nacks.receive(10_000); - assertThat(nack).isNotNull(); - assertThat(nack.getPayload()).isInstanceOf(NackedAmqpMessageException.class); - assertThat(((NackedAmqpMessageException) nack.getPayload()).getFailedMessage()).isSameAs(message); - assertThat(((NackedAmqpMessageException) nack.getPayload()).getCorrelationData()) - .isSameAs(message.getHeaders().getId()); - assertThat(((NackedAmqpMessageException) nack.getPayload()).getNackReason()).isEqualTo("Confirm timed out"); - endpoint.stop(); - } - - @Test - public void adapterWithReturns() throws Exception { - this.withReturns.setErrorMessageStrategy(null); - CorrelationData corrData = new CorrelationData("adapterWithReturns"); - Message message = MessageBuilder.withPayload("hello") - .setHeader("corrData", corrData) - .build(); - this.returnRequestChannel.send(message); - Message returned = returnChannel.receive(10000); - assertThat(returned).isNotNull(); - assertThat(returned.getPayload()).isEqualTo(message.getPayload()); - Confirm confirm = corrData.getFuture().get(10, TimeUnit.SECONDS); - assertThat(confirm).isNotNull(); - assertThat(confirm.ack()).isTrue(); - assertThat(corrData.getReturned()).isNotNull(); - } - - @Test - public void adapterWithReturnsAndErrorMessageStrategy() { - Message message = MessageBuilder.withPayload("hello").build(); - this.returnRequestChannel.send(message); - Message returned = returnChannel.receive(10000); - assertThat(returned).isNotNull(); - assertThat(returned).isInstanceOf(ErrorMessage.class); - assertThat(returned.getPayload()).isInstanceOf(ReturnedAmqpMessageException.class); - ReturnedAmqpMessageException payload = (ReturnedAmqpMessageException) returned.getPayload(); - assertThat(payload.getFailedMessage().getPayload()).isEqualTo(message.getPayload()); - } - - @Test - public void adapterWithContentType() throws Exception { - RabbitTemplate template = new RabbitTemplate(this.connectionFactory); - template.setDefaultReceiveQueue(this.queue.getName()); - while (template.receive() != null) { - // drain - } - Message message = MessageBuilder.withPayload("hello") - .setHeader(AmqpHeaders.CONTENT_TYPE, "application/json") - .build(); - this.ctRequestChannel.send(message); - org.springframework.amqp.core.Message m = receive(template); - assertThat(m).isNotNull(); - assertThat(new String(m.getBody(), "UTF-8")).isEqualTo("\"hello\""); - assertThat(m.getMessageProperties().getContentType()).isEqualTo("application/json"); - assertThat(m.getMessageProperties().getHeaders().get(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))) - .isEqualTo("java.lang.String"); - message = MessageBuilder.withPayload("hello") - .build(); - this.ctRequestChannel.send(message); - m = receive(template); - assertThat(m).isNotNull(); - assertThat(new String(m.getBody(), "UTF-8")).isEqualTo("hello"); - assertThat(m.getMessageProperties().getContentType()).isEqualTo("text/plain"); - while (template.receive() != null) { - // drain - } - } - - private org.springframework.amqp.core.Message receive(RabbitTemplate template) throws Exception { - int n = 0; - org.springframework.amqp.core.Message message = template.receive(); - while (message == null && n++ < 100) { - Thread.sleep(100); - message = template.receive(); - } - assertThat(message).isNotNull(); - return message; - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java deleted file mode 100644 index 7f74a3a8942..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.io.IOException; -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.AmqpException; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.core.QueueBuilder; -import org.springframework.amqp.core.QueueBuilder.Overflow; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.amqp.dsl.Amqp; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.2 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class AmqpOutboundEndpointTests2 implements RabbitTestContainer { - - static final String QUEUE_TEST_CONFIRM_OK = "testConfirmOk"; - - @BeforeAll - static void initQueue() throws IOException, InterruptedException { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + QUEUE_TEST_CONFIRM_OK); - } - - @AfterAll - static void deleteQueue() throws IOException, InterruptedException { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + QUEUE_TEST_CONFIRM_OK); - } - - @Test - void testConfirmOk(@Autowired IntegrationFlow flow, @Autowired RabbitTemplate template) { - flow.getInputChannel().send(new GenericMessage<>("test", Collections.singletonMap("rk", QUEUE_TEST_CONFIRM_OK))); - assertThat(template.receive(QUEUE_TEST_CONFIRM_OK)).isNotNull(); - } - - @Test - void testWithReturn(@Autowired IntegrationFlow flow) { - assertThatThrownBy(() -> flow.getInputChannel() - .send(new GenericMessage<>("test", Collections.singletonMap("rk", "junkjunk")))) - .isInstanceOf(MessageHandlingException.class) - .hasCauseInstanceOf(AmqpException.class) - .extracting(Throwable::getCause) - .extracting(Throwable::getMessage) - .isEqualTo("Message was returned by the broker"); - } - - @Test - void testReturnConfirmNoChannels(@Autowired IntegrationFlow flow2) throws Exception { - CorrelationData corr = new CorrelationData("foo"); - flow2.getInputChannel().send(MessageBuilder.withPayload("test") - .setHeader("rk", "junkjunk") - .setHeader(AmqpHeaders.PUBLISH_CONFIRM_CORRELATION, corr) - .build()); - assertThat(corr.getFuture().get(10, TimeUnit.SECONDS).ack()).isTrue(); - assertThat(corr.getReturned()).isNotNull(); - } - - @Test - void testWithReject(@Autowired IntegrationFlow flow, @Autowired RabbitAdmin admin, - @Autowired RabbitTemplate template) { - - Queue queue = QueueBuilder.nonDurable().autoDelete().maxLength(1L).overflow(Overflow.rejectPublish).build(); - admin.declareQueue(queue); - flow.getInputChannel().send(new GenericMessage<>("test", Collections.singletonMap("rk", queue.getName()))); - assertThatThrownBy(() -> flow.getInputChannel() - .send(new GenericMessage<>("test", Collections.singletonMap("rk", queue.getName())))) - .hasCauseInstanceOf(AmqpException.class) - .extracting(Throwable::getCause) - .extracting(Throwable::getMessage) - .matches(msg -> msg.matches("Negative publisher confirm received: .*")); - assertThat(template.receive(queue.getName())).isNotNull(); - admin.deleteQueue(queue.getName()); - } - - @Configuration(proxyBeanMethods = false) - @EnableIntegration - public static class Config { - - @Bean - public IntegrationFlow flow(RabbitTemplate template) { - return f -> f.handle(Amqp.outboundAdapter(template) - .exchangeName("") - .routingKeyFunction(msg -> msg.getHeaders().get("rk", String.class)) - .confirmCorrelationFunction(msg -> msg) - .waitForConfirm(true)); - } - - @Bean - public IntegrationFlow flow2(RabbitTemplate template) { - return f -> f.handle(Amqp.outboundAdapter(template) - .exchangeName("") - .routingKeyFunction(msg -> msg.getHeaders().get("rk", String.class))); - } - - @Bean - public CachingConnectionFactory cf() { - CachingConnectionFactory ccf = new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); - ccf.setPublisherReturns(true); - return ccf; - } - - @Bean - public RabbitTemplate template(ConnectionFactory cf) { - RabbitTemplate rabbitTemplate = new RabbitTemplate(cf); - rabbitTemplate.setMandatory(true); - rabbitTemplate.setReceiveTimeout(10_000); - return rabbitTemplate; - } - - @Bean - public RabbitAdmin admin(ConnectionFactory cf) { - return new RabbitAdmin(cf); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AsyncAmqpGatewayTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AsyncAmqpGatewayTests.java deleted file mode 100644 index 263f0016774..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AsyncAmqpGatewayTests.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.io.IOException; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; - -import org.springframework.amqp.core.AmqpReplyTimeoutException; -import org.springframework.amqp.rabbit.AsyncRabbitTemplate; -import org.springframework.amqp.rabbit.RabbitMessageFuture; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; -import org.springframework.amqp.rabbit.listener.adapter.ReplyingMessageListener; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.utils.test.TestUtils; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.amqp.support.NackedAmqpMessageException; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.amqp.support.ReturnedAmqpMessageException; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.support.ErrorMessage; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -class AsyncAmqpGatewayTests implements RabbitTestContainer, TestApplicationContextAware { - - static final String ASYNC_QUEUE = "asyncQ1"; - - static final String ASYNC_REPLY_QUEUE = "asyncRQ1"; - - @BeforeAll - static void initQueue() throws IOException, InterruptedException { - for (String queue : List.of(ASYNC_QUEUE, ASYNC_REPLY_QUEUE)) { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + queue); - } - } - - @AfterAll - static void deleteQueue() throws IOException, InterruptedException { - for (String queue : List.of(ASYNC_QUEUE, ASYNC_REPLY_QUEUE)) { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + queue); - } - } - - @Test - void testConfirmsAndReturns() throws Exception { - CachingConnectionFactory ccf = new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); - ccf.setPublisherReturns(true); - RabbitTemplate template = new RabbitTemplate(ccf); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(ccf); - container.setBeanName("replyContainer"); - container.setQueueNames(ASYNC_REPLY_QUEUE); - container.afterPropertiesSet(); - container.start(); - AsyncRabbitTemplate asyncTemplate = new AsyncRabbitTemplate(template, container); - asyncTemplate.setEnableConfirms(true); - asyncTemplate.setMandatory(true); - - SimpleMessageListenerContainer receiver = new SimpleMessageListenerContainer(ccf); - receiver.setBeanName("receiver"); - receiver.setQueueNames(ASYNC_QUEUE); - final CountDownLatch waitForAckBeforeReplying = new CountDownLatch(1); - MessageListenerAdapter messageListener = new MessageListenerAdapter( - (ReplyingMessageListener) foo -> { - try { - waitForAckBeforeReplying.await(10, TimeUnit.SECONDS); - } - catch (@SuppressWarnings("unused") InterruptedException e) { - Thread.currentThread().interrupt(); - } - return foo.toUpperCase(); - }); - receiver.setMessageListener(messageListener); - receiver.afterPropertiesSet(); - receiver.start(); - - AsyncAmqpOutboundGateway gateway = new AsyncAmqpOutboundGateway(asyncTemplate); - LogAccessor logger = spy(TestUtils.getPropertyValue(gateway, "logger", LogAccessor.class)); - given(logger.isDebugEnabled()).willReturn(true); - final CountDownLatch replyTimeoutLatch = new CountDownLatch(1); - willAnswer(invocation -> { - invocation.callRealMethod(); - replyTimeoutLatch.countDown(); - return null; - }).given(logger) - .debug(ArgumentMatchers.>argThat(logMessage -> - logMessage.get().startsWith("Reply not required and async timeout for"))); - new DirectFieldAccessor(gateway).setPropertyValue("logger", logger); - QueueChannel outputChannel = new QueueChannel(); - outputChannel.setBeanName("output"); - QueueChannel returnChannel = new QueueChannel(); - returnChannel.setBeanName("returns"); - QueueChannel ackChannel = new QueueChannel(); - ackChannel.setBeanName("acks"); - QueueChannel errorChannel = new QueueChannel(); - errorChannel.setBeanName("errors"); - gateway.setOutputChannel(outputChannel); - gateway.setReturnChannel(returnChannel); - gateway.setConfirmAckChannel(ackChannel); - gateway.setConfirmNackChannel(ackChannel); - gateway.setConfirmCorrelationExpressionString("#this"); - gateway.setExchangeName(""); - gateway.setRoutingKey("asyncQ1"); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - gateway.start(); - - Message message = MessageBuilder.withPayload("foo").setErrorChannel(errorChannel).build(); - - gateway.handleMessage(message); - - Message ack = ackChannel.receive(10000); - assertThat(ack).isNotNull(); - assertThat(ack.getPayload()).isEqualTo("foo"); - assertThat(ack.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM)).isEqualTo(true); - waitForAckBeforeReplying.countDown(); - - Message received = outputChannel.receive(10000); - assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo("FOO"); - - // timeout tests - asyncTemplate.setReceiveTimeout(10); - - receiver.setMessageListener(message1 -> { - }); - // reply timeout with no requiresReply - message = MessageBuilder.withPayload("bar").setErrorChannel(errorChannel).build(); - gateway.handleMessage(message); - assertThat(replyTimeoutLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - // reply timeout with requiresReply - gateway.setRequiresReply(true); - message = MessageBuilder.withPayload("baz").setErrorChannel(errorChannel).build(); - gateway.handleMessage(message); - - received = errorChannel.receive(10000); - assertThat(received).isInstanceOf(ErrorMessage.class); - ErrorMessage error = (ErrorMessage) received; - assertThat(error.getPayload()).isInstanceOf(MessagingException.class); - assertThat(error.getPayload().getCause()).isInstanceOf(AmqpReplyTimeoutException.class); - asyncTemplate.setReceiveTimeout(30000); - receiver.setMessageListener(messageListener); - - // error on sending result - DirectChannel errorForce = new DirectChannel(); - errorForce.setBeanName("errorForce"); - errorForce.subscribe(message1 -> { - throw new RuntimeException("intentional"); - }); - gateway.setOutputChannel(errorForce); - message = MessageBuilder.withPayload("qux").setErrorChannel(errorChannel).build(); - gateway.handleMessage(message); - received = errorChannel.receive(10000); - assertThat(received).isInstanceOf(ErrorMessage.class); - error = (ErrorMessage) received; - assertThat(error.getPayload()).isInstanceOf(MessagingException.class); - assertThat(((MessagingException) error.getPayload()).getFailedMessage().getPayload()).isEqualTo("QUX"); - - gateway.setRoutingKey(UUID.randomUUID().toString()); - message = MessageBuilder.withPayload("fiz").setErrorChannel(errorChannel).build(); - gateway.handleMessage(message); - Message returned = returnChannel.receive(10000); - assertThat(returned).isNotNull(); - assertThat(returned).isInstanceOf(ErrorMessage.class); - assertThat(returned.getPayload()).isInstanceOf(ReturnedAmqpMessageException.class); - ReturnedAmqpMessageException payload = (ReturnedAmqpMessageException) returned.getPayload(); - assertThat(payload.getFailedMessage().getPayload()).isEqualTo("fiz"); - ackChannel.receive(10000); - ackChannel.purge(null); - - RabbitMessageFuture future = mock(RabbitMessageFuture.class); - willReturn("nacknack").given(future).getNackCause(); - willReturn(CompletableFuture.completedFuture(false)).given(future).getConfirm(); - - asyncTemplate = mock(AsyncRabbitTemplate.class); - willReturn(future).given(asyncTemplate).sendAndReceive(anyString(), anyString(), - any(org.springframework.amqp.core.Message.class)); - new DirectFieldAccessor(gateway).setPropertyValue("template", asyncTemplate); - - message = MessageBuilder.withPayload("buz").setErrorChannel(errorChannel).build(); - gateway.handleMessage(message); - - ack = ackChannel.receive(10000); - assertThat(ack).isNotNull(); - assertThat(returned).isInstanceOf(ErrorMessage.class); - assertThat(returned.getPayload()).isInstanceOf(ReturnedAmqpMessageException.class); - NackedAmqpMessageException nack = (NackedAmqpMessageException) ack.getPayload(); - assertThat(nack.getFailedMessage().getPayload()).isEqualTo("buz"); - assertThat(nack.getNackReason()).isEqualTo("nacknack"); - - asyncTemplate.stop(); - receiver.stop(); - ccf.destroy(); - } - - @Test - void confirmsAndReturnsNoChannels() throws Exception { - CachingConnectionFactory ccf = new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); - ccf.setPublisherReturns(true); - RabbitTemplate template = new RabbitTemplate(ccf); - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(ccf); - container.setBeanName("replyContainer"); - container.setQueueNames(ASYNC_REPLY_QUEUE); - container.afterPropertiesSet(); - container.start(); - AsyncRabbitTemplate asyncTemplate = new AsyncRabbitTemplate(template, container); - asyncTemplate.setEnableConfirms(true); - asyncTemplate.setMandatory(true); - - AsyncAmqpOutboundGateway gateway = new AsyncAmqpOutboundGateway(asyncTemplate); - gateway.setOutputChannel(new NullChannel()); - gateway.setExchangeName(""); - gateway.setRoutingKey("noRoute"); - gateway.setBeanFactory(mock(BeanFactory.class)); - gateway.afterPropertiesSet(); - gateway.start(); - - CorrelationData corr = new CorrelationData("foo"); - gateway.handleMessage(MessageBuilder.withPayload("test") - .setHeader(AmqpHeaders.PUBLISH_CONFIRM_CORRELATION, corr) - .build()); - assertThat(corr.getFuture().get(10, TimeUnit.SECONDS).ack()).isTrue(); - assertThat(corr.getReturned()).isNotNull(); - - asyncTemplate.stop(); - ccf.destroy(); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/OutboundEndpointTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/OutboundEndpointTests.java deleted file mode 100644 index deab883f9da..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/OutboundEndpointTests.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.util.Date; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.AsyncRabbitTemplate; -import org.springframework.amqp.rabbit.RabbitMessageFuture; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.connection.CorrelationData; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.support.converter.DefaultJacksonJavaTypeMapper; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.ResolvableType; -import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.mapping.support.JsonHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.scheduling.TaskScheduler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willDoNothing; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 3.0 - */ -public class OutboundEndpointTests implements TestApplicationContextAware { - - @Test - public void testDelayExpression() { - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - RabbitTemplate amqpTemplate = spy(new RabbitTemplate(connectionFactory)); - AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(amqpTemplate); - willDoNothing() - .given(amqpTemplate).send(anyString(), anyString(), any(Message.class), isNull()); - willAnswer(invocation -> invocation.getArgument(2)) - .given(amqpTemplate) - .sendAndReceive(anyString(), anyString(), any(Message.class), isNull()); - endpoint.setExchangeName("foo"); - endpoint.setRoutingKey("bar"); - endpoint.setDelayExpressionString("42"); - endpoint.setBeanFactory(mock(BeanFactory.class)); - endpoint.setBeanFactory(TEST_INTEGRATION_CONTEXT); - endpoint.afterPropertiesSet(); - endpoint.handleMessage(new GenericMessage<>("foo")); - ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); - verify(amqpTemplate).send(eq("foo"), eq("bar"), captor.capture(), isNull()); - assertThat(captor.getValue().getMessageProperties().getDelayLong()).isEqualTo(42); - endpoint.setExpectReply(true); - endpoint.setOutputChannel(new NullChannel()); - endpoint.handleMessage(new GenericMessage<>("foo")); - verify(amqpTemplate).sendAndReceive(eq("foo"), eq("bar"), captor.capture(), isNull()); - assertThat(captor.getValue().getMessageProperties().getDelayLong()).isEqualTo(42); - - endpoint.setDelay(23); - endpoint.setRoutingKey("baz"); - endpoint.afterPropertiesSet(); - endpoint.handleMessage(new GenericMessage<>("foo")); - verify(amqpTemplate).sendAndReceive(eq("foo"), eq("baz"), captor.capture(), isNull()); - assertThat(captor.getValue().getMessageProperties().getDelayLong()).isEqualTo(23); - } - - @Test - public void testAsyncDelayExpression() { - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - AsyncRabbitTemplate amqpTemplate = spy(new AsyncRabbitTemplate(new RabbitTemplate(connectionFactory), - new SimpleMessageListenerContainer(connectionFactory), "replyTo")); - amqpTemplate.setTaskScheduler(mock(TaskScheduler.class)); - AsyncAmqpOutboundGateway gateway = new AsyncAmqpOutboundGateway(amqpTemplate); - willReturn(mock(RabbitMessageFuture.class)) - .given(amqpTemplate) - .sendAndReceive(anyString(), anyString(), any(Message.class)); - gateway.setExchangeName("foo"); - gateway.setRoutingKey("bar"); - gateway.setDelayExpressionString("42"); - gateway.setBeanFactory(mock(BeanFactory.class)); - gateway.setOutputChannel(new NullChannel()); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - gateway.start(); - ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); - gateway.handleMessage(new GenericMessage<>("foo")); - verify(amqpTemplate).sendAndReceive(eq("foo"), eq("bar"), captor.capture()); - assertThat(captor.getValue().getMessageProperties().getDelayLong()).isEqualTo(42); - } - - @Test - public void testHeaderMapperWinsAdapter() { - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - RabbitTemplate amqpTemplate = spy(new RabbitTemplate(connectionFactory)); - AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(amqpTemplate); - endpoint.setHeadersMappedLast(true); - final AtomicReference amqpMessage = new AtomicReference<>(); - willAnswer(invocation -> { - amqpMessage.set(invocation.getArgument(2)); - return null; - }).given(amqpTemplate).send(isNull(), isNull(), any(Message.class), isNull()); - org.springframework.messaging.Message message = MessageBuilder.withPayload("foo") - .setHeader(MessageHeaders.CONTENT_TYPE, "bar") - .build(); - endpoint.handleMessage(message); - assertThat(amqpMessage.get()).isNotNull(); - assertThat(amqpMessage.get().getMessageProperties().getContentType()).isEqualTo("bar"); - } - - @Test - public void testHeaderMapperWinsGateway() { - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - TestRabbitTemplate amqpTemplate = spy(new TestRabbitTemplate(connectionFactory)); - amqpTemplate.setUseTemporaryReplyQueues(true); - AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(amqpTemplate); - endpoint.setHeadersMappedLast(true); - endpoint.setExpectReply(true); - DefaultAmqpHeaderMapper mapper = DefaultAmqpHeaderMapper.inboundMapper(); - mapper.setRequestHeaderNames("*"); - endpoint.setHeaderMapper(mapper); - final AtomicReference amqpMessage = new AtomicReference<>(); - willAnswer(invocation -> { - amqpMessage.set(invocation.getArgument(2)); - return null; - }).given(amqpTemplate) - .doSendAndReceiveWithTemporary(isNull(), isNull(), any(Message.class), isNull()); - org.springframework.messaging.Message message = MessageBuilder.withPayload("foo") - .setHeader(MessageHeaders.CONTENT_TYPE, "bar") - .setReplyChannel(new QueueChannel()) - .build(); - endpoint.handleMessage(message); - assertThat(amqpMessage.get()).isNotNull(); - assertThat(amqpMessage.get().getMessageProperties().getContentType()).isEqualTo("bar"); - assertThat(amqpMessage.get().getMessageProperties().getHeaders().get(MessageHeaders.REPLY_CHANNEL)).isNull(); - } - - @Test - public void testReplyHeadersWin() { - ConnectionFactory connectionFactory = mock(ConnectionFactory.class); - TestRabbitTemplate amqpTemplate = spy(new TestRabbitTemplate(connectionFactory)); - amqpTemplate.setUseTemporaryReplyQueues(true); - AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(amqpTemplate); - endpoint.setExpectReply(true); - willAnswer(invocation -> - org.springframework.amqp.core.MessageBuilder.withBody(new byte[0]) - .setHeader(DefaultJacksonJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, String.class.getName()) - .build() - ).given(amqpTemplate) - .doSendAndReceiveWithTemporary(isNull(), isNull(), any(Message.class), isNull()); - QueueChannel replyChannel = new QueueChannel(); - org.springframework.messaging.Message message = MessageBuilder.withPayload("foo") - .setHeader(JsonHeaders.RESOLVABLE_TYPE, ResolvableType.forClass(Date.class)) - .setReplyChannel(replyChannel) - .build(); - endpoint.handleMessage(message); - org.springframework.messaging.Message receive = replyChannel.receive(10_000); - assertThat(receive).isNotNull(); - assertThat(receive.getHeaders()) - .containsEntry(DefaultJacksonJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, String.class.getName()) - .containsEntry(JsonHeaders.TYPE_ID, String.class.getName()) - .containsEntry(JsonHeaders.RESOLVABLE_TYPE, ResolvableType.forClass(String.class)); - } - - /** - * Increase method visibility - */ - private class TestRabbitTemplate extends RabbitTemplate { - - private TestRabbitTemplate(ConnectionFactory connectionFactory) { - super(connectionFactory); - } - - @Override - public org.springframework.amqp.core.Message doSendAndReceiveWithTemporary(String exchange, - String routingKey, org.springframework.amqp.core.Message message, CorrelationData correlationData) { - return super.doSendAndReceiveWithTemporary(exchange, routingKey, message, correlationData); - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/RabbitStreamMessageHandlerTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/RabbitStreamMessageHandlerTests.java deleted file mode 100644 index 1435f0e7084..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/RabbitStreamMessageHandlerTests.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.outbound; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import com.rabbitmq.stream.Consumer; -import com.rabbitmq.stream.Environment; -import com.rabbitmq.stream.Message; -import com.rabbitmq.stream.OffsetSpecification; -import com.rabbitmq.stream.codec.SimpleCodec; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; - -import org.springframework.integration.amqp.dsl.RabbitStream; -import org.springframework.integration.amqp.support.RabbitTestContainer; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; -import org.springframework.rabbit.stream.producer.StreamSendException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Gary Russell - * @author Chris Bono - * @author Artem Bilan - * @author Ryan Riley - * - * @since 6.0 - */ -public class RabbitStreamMessageHandlerTests implements RabbitTestContainer { - - @Test - void convertAndSend() throws InterruptedException { - Environment env = Environment.builder() - .lazyInitialization(true) - .port(RabbitTestContainer.streamPort()) - .build(); - try { - env.deleteStream("stream.stream"); - } - catch (Exception e) { - } - env.streamCreator().stream("stream.stream").create(); - RabbitStreamTemplate streamTemplate = new RabbitStreamTemplate(env, "stream.stream"); - - RabbitStreamMessageHandler handler = RabbitStream.outboundStreamAdapter(streamTemplate) - .sync(true) - .getObject(); - - handler.handleMessage(MessageBuilder.withPayload("foo") - .setHeader("bar", "baz") - .build()); - CountDownLatch latch = new CountDownLatch(1); - AtomicReference received = new AtomicReference<>(); - Consumer consumer = env.consumerBuilder().stream("stream.stream") - .offset(OffsetSpecification.first()) - .messageHandler((context, msg) -> { - received.set(msg); - latch.countDown(); - }) - .build(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(received.get()).isNotNull(); - assertThat(received.get().getBodyAsBinary()).isEqualTo("foo".getBytes()); - assertThat((String) received.get().getApplicationProperties().get("bar")).isEqualTo("baz"); - consumer.close(); - streamTemplate.close(); - } - - @Test - void sendNative() throws InterruptedException { - Environment env = Environment.builder() - .port(RabbitTestContainer.streamPort()) - .lazyInitialization(true) - .build(); - try { - env.deleteStream("stream.stream"); - } - catch (Exception e) { - } - env.streamCreator().stream("stream.stream").create(); - RabbitStreamTemplate streamTemplate = new RabbitStreamTemplate(env, "stream.stream"); - RabbitStreamMessageHandler handler = new RabbitStreamMessageHandler(streamTemplate); - handler.setSync(true); - handler.handleMessage(MessageBuilder.withPayload(streamTemplate.messageBuilder() - .addData("foo".getBytes()) - .applicationProperties().entry("bar", "baz") - .messageBuilder() - .build()) - .build()); - CountDownLatch latch = new CountDownLatch(1); - AtomicReference received = new AtomicReference<>(); - Consumer consumer = env.consumerBuilder().stream("stream.stream") - .offset(OffsetSpecification.first()) - .messageHandler((context, msg) -> { - received.set(msg); - latch.countDown(); - }) - .build(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(received.get()).isNotNull(); - assertThat(received.get().getBodyAsBinary()).isEqualTo("foo".getBytes()); - assertThat((String) received.get().getApplicationProperties().get("bar")).isEqualTo("baz"); - consumer.close(); - streamTemplate.close(); - } - - @Test - void errorChanelAsync() { - Environment env = Mockito.mock(Environment.class); - RabbitStreamTemplate streamTemplate = new RabbitStreamTemplate(env, "stream.stream"); - RabbitStreamTemplate spyStreamTemplate = Mockito.spy(streamTemplate); - CompletableFuture> errorFuture = new CompletableFuture<>(); - Mockito.doReturn(errorFuture).when(spyStreamTemplate).send(ArgumentMatchers.any(Message.class)); - - QueueChannel errorChannel = new QueueChannel(); - RabbitStreamMessageHandler handler = RabbitStream.outboundStreamAdapter(spyStreamTemplate) - .sync(false) - .sendFailureChannel(errorChannel) - .getObject(); - SimpleCodec codec = new SimpleCodec(); - org.springframework.messaging.Message testMessage = MessageBuilder.withPayload(codec.messageBuilder() - .addData(new byte[1]) - .build()) - .build(); - handler.handleMessage(testMessage); - StreamSendException streamException = new StreamSendException("Test Error Code", 99); - errorFuture.completeExceptionally(streamException); - ErrorMessage errorMessage = (ErrorMessage) errorChannel.receive(1000); - assertThat(errorMessage).extracting(org.springframework.messaging.Message::getPayload).isEqualTo(streamException); - } - - @Test - void errorChanelSync() { - Environment env = Mockito.mock(Environment.class); - RabbitStreamTemplate streamTemplate = new RabbitStreamTemplate(env, "stream.stream"); - RabbitStreamTemplate spyStreamTemplate = Mockito.spy(streamTemplate); - CompletableFuture> errorFuture = new CompletableFuture<>(); - errorFuture.exceptionally(ErrorMessage::new); - Mockito.doReturn(errorFuture).when(spyStreamTemplate).send(ArgumentMatchers.any(Message.class)); - - QueueChannel errorChannel = new QueueChannel(); - RabbitStreamMessageHandler handler = RabbitStream.outboundStreamAdapter(spyStreamTemplate) - .sync(true) - .sendFailureChannel(errorChannel) - .getObject(); - SimpleCodec codec = new SimpleCodec(); - org.springframework.messaging.Message testMessage = MessageBuilder.withPayload(codec.messageBuilder() - .addData(new byte[1]) - .build()) - .build(); - assertThatExceptionOfType(MessageHandlingException.class) - .isThrownBy(() -> handler.handleMessage(testMessage)); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdviceIntegrationTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdviceIntegrationTests.java deleted file mode 100644 index 2f761913745..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdviceIntegrationTests.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.logging.Log; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.amqp.dsl.Amqp; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.spy; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.1 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class BoundRabbitChannelAdviceIntegrationTests implements RabbitTestContainer { - - static final String QUEUE = "dedicated.advice"; - - @BeforeAll - static void initQueue() throws IOException, InterruptedException { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + QUEUE); - } - - @AfterAll - static void deleteQueue() throws IOException, InterruptedException { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + QUEUE); - } - - @Autowired - private Config.Gate gate; - - @Autowired - private Config config; - - @Test - void testAdvice() throws Exception { - BoundRabbitChannelAdvice advice = this.config.advice(this.config.template()); - Log logger = spy(TestUtils.getPropertyValue(advice, "logger", Log.class)); - new DirectFieldAccessor(advice).setPropertyValue("logger", logger); - willReturn(true).given(logger).isDebugEnabled(); - final CountDownLatch latch = new CountDownLatch(1); - willAnswer(i -> { - latch.countDown(); - return i.callRealMethod(); - }).given(logger).debug(anyString()); - this.gate.send("a,b,c"); - assertThat(this.config.latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(this.config.received).containsExactly("A", "B", "C"); - } - - @Configuration - @EnableIntegration - public static class Config { - - private final CountDownLatch latch = new CountDownLatch(3); - - private final List received = new ArrayList<>(); - - @Bean - public CachingConnectionFactory cf() { - CachingConnectionFactory ccf = new CachingConnectionFactory(RabbitTestContainer.amqpPort()); - ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE); - return ccf; - } - - @Bean - public RabbitTemplate template() { - return new RabbitTemplate(cf()); - } - - @Bean - public BoundRabbitChannelAdvice advice(RabbitTemplate template) { - return new BoundRabbitChannelAdvice(template, Duration.ofSeconds(10)); - } - - @Bean - public IntegrationFlow flow(RabbitTemplate template, BoundRabbitChannelAdvice advice) { - return IntegrationFlow.from(Gate.class) - .splitWith(s -> s.delimiters(",").advice(advice)) - .transform(String::toUpperCase) - .handle(Amqp.outboundAdapter(template).routingKey(QUEUE)) - .get(); - } - - @Bean - public IntegrationFlow listener(CachingConnectionFactory ccf) { - return IntegrationFlow.from(Amqp.inboundAdapter(ccf, QUEUE)) - .handle(m -> { - received.add((String) m.getPayload()); - this.latch.countDown(); - }) - .get(); - } - - public interface Gate { - - void send(String out); - - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdviceTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdviceTests.java deleted file mode 100644 index 3e6e0e3ca5b..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/BoundRabbitChannelAdviceTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.time.Duration; -import java.util.concurrent.ExecutorService; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitOperations; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.amqp.dsl.Amqp; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.1 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class BoundRabbitChannelAdviceTests { - - @Autowired - private Config.Gate gate; - - @Autowired - private Config config; - - @Test - void testAdvice() throws Exception { - this.gate.send("a,b,c"); - verify(this.config.connection, times(1)).createChannel(); - verify(this.config.channel).confirmSelect(); - verify(this.config.channel).basicPublish(eq(""), eq("rk"), anyBoolean(), any(), eq("A".getBytes())); - verify(this.config.channel).basicPublish(eq(""), eq("rk"), anyBoolean(), any(), eq("B".getBytes())); - verify(this.config.channel).basicPublish(eq(""), eq("rk"), anyBoolean(), any(), eq("C".getBytes())); - verify(this.config.channel).waitForConfirmsOrDie(10_000L); - } - - @Test - void validate() { - RabbitOperations template = mock(RabbitOperations.class); - given(template.getConnectionFactory()).willReturn( - mock(org.springframework.amqp.rabbit.connection.ConnectionFactory.class)); - assertThatIllegalArgumentException().isThrownBy(() -> - new BoundRabbitChannelAdvice(template, Duration.ofSeconds(1))); - } - - @Configuration - @EnableIntegration - public static class Config { - - private Connection connection; - - private Channel channel; - - @Bean - public CachingConnectionFactory cf() throws Exception { - ConnectionFactory cf = mock(ConnectionFactory.class); - cf.setHost("localhost"); - willAnswer(i -> { - this.connection = mock(Connection.class); - willAnswer(ii -> { - this.channel = mock(Channel.class); - given(this.channel.isOpen()).willReturn(true); - return this.channel; - }).given(this.connection).createChannel(); - return this.connection; - }).given(cf).newConnection((ExecutorService) isNull(), anyString()); - cf.setAutomaticRecoveryEnabled(false); - CachingConnectionFactory ccf = new CachingConnectionFactory(cf); - ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE); - return ccf; - } - - @Bean - public RabbitTemplate template() throws Exception { - return new RabbitTemplate(cf()); - } - - @Bean - public IntegrationFlow flow(RabbitTemplate template) { - return IntegrationFlow.from(Gate.class) - .splitWith(s -> s.delimiters(",") - .advice(new BoundRabbitChannelAdvice(template, Duration.ofSeconds(10)))) - .transform(String::toUpperCase) - .handle(Amqp.outboundAdapter(template).routingKey("rk")) - .get(); - } - - public interface Gate { - - void send(String out); - - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java deleted file mode 100644 index 9dad3580b1a..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.DefaultJacksonJavaTypeMapper; -import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.core.ResolvableType; -import org.springframework.http.MediaType; -import org.springframework.integration.mapping.support.JsonHeaders; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.MimeType; -import org.springframework.util.MimeTypeUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mock; - -/** - * @author Mark Fisher - * @author Gary Russell - * @author Oleg Zhurakousky - * @author Stephane Nicoll - * @author Artem Bilan - * @author Steve Singer - * - * @since 2.1 - */ -public class DefaultAmqpHeaderMapperTests { - - @Test - public void fromHeadersFallbackIdTimestamp() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - org.springframework.messaging.Message message = new GenericMessage<>(""); - MessageProperties messageProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(message.getHeaders(), messageProperties); - assertThat(message.getHeaders().getId().toString()).isEqualTo(messageProperties.getMessageId()); - assertThat(message.getHeaders().getTimestamp()).isEqualTo(messageProperties.getTimestamp().getTime()); - } - - @Test - public void fromHeaders() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - Map headerMap = new HashMap<>(); - headerMap.put(AmqpHeaders.APP_ID, "test.appId"); - headerMap.put(AmqpHeaders.CLUSTER_ID, "test.clusterId"); - headerMap.put(AmqpHeaders.CONTENT_ENCODING, "test.contentEncoding"); - headerMap.put(AmqpHeaders.CONTENT_LENGTH, 99L); - headerMap.put(AmqpHeaders.CONTENT_TYPE, "test.contentType"); - String testCorrelationId = "foo"; - headerMap.put(AmqpHeaders.CORRELATION_ID, testCorrelationId); - headerMap.put(AmqpHeaders.DELAY, 1234L); - headerMap.put(AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.NON_PERSISTENT); - headerMap.put(AmqpHeaders.DELIVERY_TAG, 1234L); - headerMap.put(AmqpHeaders.EXPIRATION, "test.expiration"); - headerMap.put(AmqpHeaders.MESSAGE_COUNT, 42); - headerMap.put(AmqpHeaders.MESSAGE_ID, "test.messageId"); - headerMap.put(AmqpHeaders.RECEIVED_EXCHANGE, "test.receivedExchange"); - headerMap.put(AmqpHeaders.RECEIVED_ROUTING_KEY, "test.receivedRoutingKey"); - headerMap.put(AmqpHeaders.REPLY_TO, "test.replyTo"); - Date testTimestamp = new Date(); - headerMap.put(AmqpHeaders.TIMESTAMP, testTimestamp); - headerMap.put(AmqpHeaders.TYPE, "test.type"); - headerMap.put(AmqpHeaders.USER_ID, "test.userId"); - headerMap.put(AmqpHeaders.SPRING_REPLY_CORRELATION, "test.correlation"); - headerMap.put(AmqpHeaders.SPRING_REPLY_TO_STACK, "test.replyTo2"); - - headerMap.put(MessageHeaders.ERROR_CHANNEL, mock(MessageChannel.class)); - headerMap.put(MessageHeaders.REPLY_CHANNEL, mock(MessageChannel.class)); - - MessageHeaders integrationHeaders = new MessageHeaders(headerMap); - MessageProperties amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - Set headerKeys = amqpProperties.getHeaders().keySet(); - for (String headerKey : headerKeys) { - if (headerKey.startsWith(AmqpHeaders.PREFIX) || headerKey.equals("contentType")) { - fail("Unexpected header in properties.headers: " + headerKey); - } - } - assertThat(amqpProperties.getAppId()).isEqualTo("test.appId"); - assertThat(amqpProperties.getClusterId()).isEqualTo("test.clusterId"); - assertThat(amqpProperties.getContentEncoding()).isEqualTo("test.contentEncoding"); - assertThat(amqpProperties.getContentLength()).isEqualTo(99L); - assertThat(amqpProperties.getContentType()).isEqualTo("test.contentType"); - assertThat(amqpProperties.getCorrelationId()).isEqualTo(testCorrelationId); - assertThat(amqpProperties.getDelayLong()).isEqualTo(1234L); - assertThat(amqpProperties.getDeliveryMode()).isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(amqpProperties.getDeliveryTag()).isEqualTo(1234L); - assertThat(amqpProperties.getExpiration()).isEqualTo("test.expiration"); - assertThat(amqpProperties.getMessageCount()).isEqualTo(42); - assertThat(amqpProperties.getMessageId()).isEqualTo("test.messageId"); - assertThat(amqpProperties.getReceivedExchange()).isEqualTo("test.receivedExchange"); - assertThat(amqpProperties.getReceivedRoutingKey()).isEqualTo("test.receivedRoutingKey"); - assertThat(amqpProperties.getReplyTo()).isEqualTo("test.replyTo"); - assertThat(amqpProperties.getTimestamp()).isEqualTo(testTimestamp); - assertThat(amqpProperties.getType()).isEqualTo("test.type"); - assertThat(amqpProperties.getUserId()).isEqualTo("test.userId"); - - assertThat(amqpProperties.getHeaders().get(MessageHeaders.ERROR_CHANNEL)).isNull(); - assertThat(amqpProperties.getHeaders().get(MessageHeaders.REPLY_CHANNEL)).isNull(); - } - - @Test - public void fromHeadersWithContentTypeAsMediaType() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - Map headerMap = new HashMap<>(); - - MediaType contentType = MediaType.parseMediaType("text/html"); - headerMap.put(AmqpHeaders.CONTENT_TYPE, contentType); - - MessageHeaders integrationHeaders = new MessageHeaders(headerMap); - MessageProperties amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - - assertThat(amqpProperties.getContentType()).isEqualTo("text/html"); - - headerMap.put(AmqpHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON); - integrationHeaders = new MessageHeaders(headerMap); - amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - - assertThat(amqpProperties.getContentType()).isEqualTo(MimeTypeUtils.APPLICATION_JSON_VALUE); - } - - @Test - public void fromHeadersWithContentTypeAsMimeType() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - Map headerMap = new HashMap<>(); - - MimeType contentType = MimeType.valueOf("text/html"); - headerMap.put(AmqpHeaders.CONTENT_TYPE, contentType); - - MessageHeaders integrationHeaders = new MessageHeaders(headerMap); - MessageProperties amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - - assertThat(amqpProperties.getContentType()).isEqualTo("text/html"); - } - - @Test - public void toHeaders() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentEncoding("test.contentEncoding"); - amqpProperties.setContentLength(99L); - amqpProperties.setContentType("test.contentType"); - String testCorrelationId = "foo"; - amqpProperties.setCorrelationId(testCorrelationId); - amqpProperties.setReceivedDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); - amqpProperties.setDeliveryTag(1234L); - amqpProperties.setExpiration("test.expiration"); - amqpProperties.setMessageCount(42); - amqpProperties.setMessageId("test.messageId"); - amqpProperties.setPriority(22); - amqpProperties.setReceivedDelayLong(4567L); - amqpProperties.setReceivedExchange("test.receivedExchange"); - amqpProperties.setReceivedRoutingKey("test.receivedRoutingKey"); - amqpProperties.setRedelivered(true); - amqpProperties.setReplyTo("test.replyTo"); - Date testTimestamp = new Date(); - amqpProperties.setTimestamp(testTimestamp); - amqpProperties.setType("test.type"); - amqpProperties.setReceivedUserId("test.userId"); - amqpProperties.setHeader(AmqpHeaders.SPRING_REPLY_CORRELATION, "test.correlation"); - amqpProperties.setHeader(AmqpHeaders.SPRING_REPLY_TO_STACK, "test.replyTo2"); - Map headerMap = headerMapper.toHeadersFromReply(amqpProperties); - assertThat(headerMap.get(AmqpHeaders.APP_ID)).isEqualTo("test.appId"); - assertThat(headerMap.get(AmqpHeaders.CLUSTER_ID)).isEqualTo("test.clusterId"); - assertThat(headerMap.get(AmqpHeaders.CONTENT_ENCODING)).isEqualTo("test.contentEncoding"); - assertThat(headerMap.get(AmqpHeaders.CONTENT_LENGTH)).isEqualTo(99L); - assertThat(headerMap.get(AmqpHeaders.CONTENT_TYPE)).isEqualTo("test.contentType"); - assertThat(headerMap.get(AmqpHeaders.CORRELATION_ID)).isEqualTo(testCorrelationId); - assertThat(headerMap.get(AmqpHeaders.RECEIVED_DELIVERY_MODE)).isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(headerMap.get(AmqpHeaders.DELIVERY_TAG)).isEqualTo(1234L); - assertThat(headerMap.get(AmqpHeaders.EXPIRATION)).isEqualTo("test.expiration"); - assertThat(headerMap.get(AmqpHeaders.MESSAGE_COUNT)).isEqualTo(42); - assertThat(headerMap.get(AmqpHeaders.MESSAGE_ID)).isEqualTo("test.messageId"); - assertThat(headerMap.get(AmqpHeaders.RECEIVED_DELAY)).isEqualTo(4567L); - assertThat(headerMap.get(AmqpHeaders.RECEIVED_EXCHANGE)).isEqualTo("test.receivedExchange"); - assertThat(headerMap.get(AmqpHeaders.RECEIVED_ROUTING_KEY)).isEqualTo("test.receivedRoutingKey"); - assertThat(headerMap.get(AmqpHeaders.REPLY_TO)).isEqualTo("test.replyTo"); - assertThat(headerMap.get(AmqpHeaders.TIMESTAMP)).isEqualTo(testTimestamp); - assertThat(headerMap.get(AmqpHeaders.TYPE)).isEqualTo("test.type"); - assertThat(headerMap.get(AmqpHeaders.RECEIVED_USER_ID)).isEqualTo("test.userId"); - assertThat(headerMap.get(AmqpHeaders.SPRING_REPLY_CORRELATION)).isEqualTo("test.correlation"); - assertThat(headerMap.get(AmqpHeaders.SPRING_REPLY_TO_STACK)).isEqualTo("test.replyTo2"); - } - - @Test - public void toHeadersNonContentType() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setAppId("test.appId"); - amqpProperties.setClusterId("test.clusterId"); - amqpProperties.setContentType(null); - String testCorrelationId = "foo"; - amqpProperties.setCorrelationId(testCorrelationId); - Map headerMap = headerMapper.toHeadersFromReply(amqpProperties); - assertThat(headerMap.get(AmqpHeaders.CORRELATION_ID)).isEqualTo(testCorrelationId); - } - - @Test - public void testToHeadersConsumerMetadata() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setConsumerTag("consumerTag"); - amqpProperties.setConsumerQueue("consumerQueue"); - Map headerMap = headerMapper.toHeadersFromRequest(amqpProperties); - assertThat(headerMap.get(AmqpHeaders.CONSUMER_TAG)).isEqualTo("consumerTag"); - assertThat(headerMap.get(AmqpHeaders.CONSUMER_QUEUE)).isEqualTo("consumerQueue"); - } - - @Test - public void messageIdNotMappedToAmqpProperties() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - Map headerMap = new HashMap<>(); - headerMap.put(MessageHeaders.ID, "msg-id"); - MessageHeaders integrationHeaders = new MessageHeaders(headerMap); - MessageProperties amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - assertThat(amqpProperties.getHeaders().get(MessageHeaders.ID)).isNull(); - } - - @Test - public void messageTimestampNotMappedToAmqpProperties() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - Map headerMap = new HashMap<>(); - headerMap.put(MessageHeaders.TIMESTAMP, 1234L); - MessageHeaders integrationHeaders = new MessageHeaders(headerMap); - MessageProperties amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - assertThat(amqpProperties.getHeaders().get(MessageHeaders.TIMESTAMP)).isNull(); - } - - @Test - public void jsonTypeIdNotOverwritten() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - MessageConverter converter = new JacksonJsonMessageConverter(); - MessageProperties amqpProperties = new MessageProperties(); - converter.toMessage("123", amqpProperties); - Map headerMap = new HashMap<>(); - headerMap.put("__TypeId__", "java.lang.Integer"); - MessageHeaders integrationHeaders = new MessageHeaders(headerMap); - headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - assertThat(amqpProperties.getHeaders().get("__TypeId__")).isEqualTo("java.lang.String"); - Object result = converter.fromMessage(new Message("123".getBytes(), amqpProperties)); - assertThat(result.getClass()).isEqualTo(String.class); - } - - @Test - public void inboundOutbound() { - DefaultAmqpHeaderMapper mapper = DefaultAmqpHeaderMapper.inboundMapper(); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.setReceivedDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); - amqpProperties.getHeaders().put("foo", "bar"); - amqpProperties.getHeaders().put("x-foo", "bar"); - Map headers = mapper.toHeadersFromRequest(amqpProperties); - assertThat(headers.get(AmqpHeaders.DELIVERY_MODE)).isNull(); - assertThat(headers.get(AmqpHeaders.RECEIVED_DELIVERY_MODE)).isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(headers.get("foo")).isEqualTo("bar"); - assertThat(headers.get("x-foo")).isEqualTo("bar"); - - amqpProperties = new MessageProperties(); - headers.put(AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.NON_PERSISTENT); - mapper.fromHeadersToReply(new MessageHeaders(headers), amqpProperties); - assertThat(amqpProperties.getDeliveryMode()).isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(amqpProperties.getHeaders().get("foo")).isEqualTo("bar"); - assertThat(amqpProperties.getHeaders().get("x-foo")).isNull(); - - mapper = DefaultAmqpHeaderMapper.outboundMapper(); - mapper.fromHeadersToRequest(new MessageHeaders(headers), amqpProperties); - assertThat(amqpProperties.getDeliveryMode()).isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(amqpProperties.getHeaders().get("foo")).isEqualTo("bar"); - assertThat(amqpProperties.getHeaders().get("x-foo")).isNull(); - - amqpProperties.setReceivedDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); - amqpProperties.setHeader("x-death", "foo"); - headers = mapper.toHeadersFromReply(amqpProperties); - assertThat(headers.get(AmqpHeaders.RECEIVED_DELIVERY_MODE)).isEqualTo(MessageDeliveryMode.NON_PERSISTENT); - assertThat(headers.get(AmqpHeaders.DELIVERY_MODE)).isNull(); - assertThat(headers.get("foo")).isEqualTo("bar"); - assertThat(headers.get("x-death")).isEqualTo("foo"); - } - - @Test - public void jsonHeadersResolvableTypeSkipped() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - MessageHeaders integrationHeaders = - new MessageHeaders( - Collections.singletonMap(JsonHeaders.RESOLVABLE_TYPE, ResolvableType.forClass(String.class))); - MessageProperties amqpProperties = new MessageProperties(); - headerMapper.fromHeadersToReply(integrationHeaders, amqpProperties); - - assertThat(amqpProperties.getHeaders()).doesNotContainKeys(JsonHeaders.RESOLVABLE_TYPE); - } - - @Test - public void jsonHeadersNotMapped() { - DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper(); - headerMapper.setRequestHeaderNames("!json_*", "*"); - MessageProperties amqpProperties = new MessageProperties(); - amqpProperties.getHeaders().put(DefaultJacksonJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, "test.type"); - Map headers = headerMapper.toHeadersFromRequest(amqpProperties); - assertThat(headers) - .doesNotContainKeys(JsonHeaders.RESOLVABLE_TYPE, JsonHeaders.TYPE_ID) - .containsKey(DefaultJacksonJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME); - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/JsonConverterCompatibilityTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/JsonConverterCompatibilityTests.java deleted file mode 100644 index 09f9df2499d..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/JsonConverterCompatibilityTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.io.IOException; - -import com.rabbitmq.client.AMQP.BasicProperties; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter; -import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; -import org.springframework.integration.json.ObjectToJsonTransformer; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @since 4.3 - * - */ -public class JsonConverterCompatibilityTests implements RabbitTestContainer { - - static final String JSON_TESTQ = "si.json.tests"; - - @BeforeAll - static void initQueue() throws IOException, InterruptedException { - RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + JSON_TESTQ); - } - - @AfterAll - static void deleteQueue() throws IOException, InterruptedException { - RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + JSON_TESTQ); - } - - private RabbitTemplate rabbitTemplate; - - @BeforeEach - public void setUp() { - this.rabbitTemplate = new RabbitTemplate(new CachingConnectionFactory(RabbitTestContainer.amqpPort())); - this.rabbitTemplate.setMessageConverter(new JacksonJsonMessageConverter()); - } - - @AfterEach - public void tearDown() { - ((CachingConnectionFactory) this.rabbitTemplate.getConnectionFactory()).destroy(); - } - - @Test - public void testInbound() { - @SuppressWarnings("unchecked") final Message out = (Message) new ObjectToJsonTransformer() - .transform(new GenericMessage<>(new Foo())); - MessageProperties messageProperties = new MessageProperties(); - DefaultAmqpHeaderMapper.outboundMapper().fromHeadersToRequest(out.getHeaders(), messageProperties); - final BasicProperties props = new DefaultMessagePropertiesConverter().fromMessageProperties(messageProperties, - "UTF-8"); - this.rabbitTemplate.execute(channel -> { - channel.basicPublish("", JSON_TESTQ, props, out.getPayload().getBytes()); - return null; - }); - - Object received = this.rabbitTemplate.receiveAndConvert(JSON_TESTQ); - assertThat(received).isInstanceOf(Foo.class); - } - - public static class Foo { - - private String bar = "bar"; - - public String getBar() { - return this.bar; - } - - public void setBar(String bar) { - this.bar = bar; - } - - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/MappingUtilsTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/MappingUtilsTests.java deleted file mode 100644 index fae3aa3e6cc..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/MappingUtilsTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.MessageDeliveryMode; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.SimpleMessageConverter; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Gary Russell - * @since 5.0 - * - */ -public class MappingUtilsTests { - - @Test - public void testMapping() { - Message requestMessage = MessageBuilder.withPayload("foo") - .setHeader(AmqpHeaders.CONTENT_TYPE, "my/ct") - .build(); - MessageConverter converter = new SimpleMessageConverter(); - AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper(); - MessageDeliveryMode defaultDeliveryMode = MessageDeliveryMode.NON_PERSISTENT; - boolean headersMappedLast = false; - org.springframework.amqp.core.Message mapped = MappingUtils.mapMessage(requestMessage, converter, headerMapper, - defaultDeliveryMode, headersMappedLast); - assertThat(mapped.getMessageProperties().getContentType()).isEqualTo("text/plain"); - - headersMappedLast = true; - mapped = MappingUtils.mapMessage(requestMessage, converter, headerMapper, - defaultDeliveryMode, headersMappedLast); - assertThat(mapped.getMessageProperties().getContentType()).isEqualTo("my/ct"); - - ContentTypeDelegatingMessageConverter ctdConverter = new ContentTypeDelegatingMessageConverter(); - ctdConverter.addDelegate("my/ct", converter); - mapped = MappingUtils.mapMessage(requestMessage, ctdConverter, headerMapper, - defaultDeliveryMode, headersMappedLast); - assertThat(mapped.getMessageProperties().getContentType()).isEqualTo("my/ct"); - - headersMappedLast = false; - mapped = MappingUtils.mapMessage(requestMessage, ctdConverter, headerMapper, - defaultDeliveryMode, headersMappedLast); - assertThat(mapped.getMessageProperties().getContentType()).isEqualTo("text/plain"); - - headersMappedLast = true; - requestMessage = MessageBuilder.withPayload("foo") - .setHeader(AmqpHeaders.CONTENT_TYPE, 42) - .build(); - try { - mapped = MappingUtils.mapMessage(requestMessage, ctdConverter, headerMapper, - defaultDeliveryMode, headersMappedLast); - fail("Expected IllegalArgumentException"); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage()) - .isEqualTo("contentType header must be a MimeType or String, found: java.lang.Integer"); - } - } - -} diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/RabbitTestContainer.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/RabbitTestContainer.java deleted file mode 100644 index 0c1b76e0c9e..00000000000 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/RabbitTestContainer.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.amqp.support; - -import java.io.IOException; -import java.time.Duration; - -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.RabbitMQContainer; -import org.testcontainers.junit.jupiter.Testcontainers; - -/** - * Provides a static {@link RabbitMQContainer} that can be shared across test classes. - * - * @author Chris Bono - * @author Gary Russell - * @author Artem Bilan - */ -@Testcontainers(disabledWithoutDocker = true) -public interface RabbitTestContainer { - - RabbitMQContainer RABBITMQ = new RabbitMQContainer("rabbitmq:management") - .withExposedPorts(5672, 15672, 5552) - .withStartupTimeout(Duration.ofMinutes(2)); - - @BeforeAll - static void startContainer() throws IOException, InterruptedException { - RABBITMQ.start(); - RABBITMQ.execInContainer("rabbitmq-plugins", "enable", "rabbitmq_stream"); - } - - static int amqpPort() { - return RABBITMQ.getMappedPort(5672); - } - - static int managementPort() { - return RABBITMQ.getMappedPort(15672); - } - - static int streamPort() { - return RABBITMQ.getMappedPort(5552); - } - -} diff --git a/spring-integration-amqp/src/test/resources/log4j2-test.xml b/spring-integration-amqp/src/test/resources/log4j2-test.xml deleted file mode 100644 index 6961531500e..00000000000 --- a/spring-integration-amqp/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/spring-integration-bom/spring-integration-bom.txt b/spring-integration-bom/spring-integration-bom.txt deleted file mode 100644 index 9bf40121445..00000000000 --- a/spring-integration-bom/spring-integration-bom.txt +++ /dev/null @@ -1 +0,0 @@ -This meta-project is used to generate a bill-of-materials POM that contains the other projects in a dependencyManagement section. diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/Camel.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/Camel.java deleted file mode 100644 index 323e768b9e7..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/Camel.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.camel.dsl; - -import org.apache.camel.ExchangePattern; -import org.apache.camel.ProducerTemplate; -import org.apache.camel.builder.LambdaRouteBuilder; -import org.jspecify.annotations.Nullable; - -/** - * Factory class for Apache Camel components DSL. - * - * @author Artem Bilan - * - * @since 6.0 - */ -public final class Camel { - - /** - * Create an instance of {@link CamelMessageHandlerSpec} in a {@link ExchangePattern#InOnly} mode. - * @return the spec. - */ - public static CamelMessageHandlerSpec handler() { - return camelHandler(null, ExchangePattern.InOnly); - } - - /** - * Create an instance of {@link CamelMessageHandlerSpec} for the provided {@link ProducerTemplate} - * in a {@link ExchangePattern#InOnly} mode. - * @param producerTemplate the {@link ProducerTemplate} to use. - * @return the spec. - */ - public static CamelMessageHandlerSpec handler(ProducerTemplate producerTemplate) { - return camelHandler(producerTemplate, ExchangePattern.InOnly); - } - - /** - * Create an instance of {@link CamelMessageHandlerSpec} in a {@link ExchangePattern#InOut} mode. - * @return the spec. - */ - public static CamelMessageHandlerSpec gateway() { - return camelHandler(null, ExchangePattern.InOut); - } - - /** - * Create an instance of {@link CamelMessageHandlerSpec} for the provided {@link ProducerTemplate} - * in a {@link ExchangePattern#InOut} mode. - * @param producerTemplate the {@link ProducerTemplate} to use. - * @return the spec. - */ - public static CamelMessageHandlerSpec gateway(ProducerTemplate producerTemplate) { - return camelHandler(producerTemplate, ExchangePattern.InOut); - } - - /** - * Create an instance of {@link CamelMessageHandlerSpec} for the provided {@link LambdaRouteBuilder} - * in a {@link ExchangePattern#InOut} mode. - * The {@code CamelContext} is fetched as a bean from the application context. - * @param route the {@link LambdaRouteBuilder} to use. - * @return the spec. - */ - public static CamelMessageHandlerSpec route(LambdaRouteBuilder route) { - return camelHandler(null, ExchangePattern.InOut) - .route(route); - } - - private static CamelMessageHandlerSpec camelHandler(@Nullable ProducerTemplate producerTemplate, - ExchangePattern exchangePattern) { - - return new CamelMessageHandlerSpec(producerTemplate) - .exchangePattern(exchangePattern); - } - - private Camel() { - } - -} diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/CamelMessageHandlerSpec.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/CamelMessageHandlerSpec.java deleted file mode 100644 index 22eed3b9e00..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/CamelMessageHandlerSpec.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.camel.dsl; - -import java.util.Arrays; -import java.util.Map; -import java.util.function.Function; - -import org.apache.camel.ExchangePattern; -import org.apache.camel.ProducerTemplate; -import org.apache.camel.builder.LambdaRouteBuilder; -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.camel.outbound.CamelMessageHandler; -import org.springframework.integration.camel.support.CamelHeaderMapper; -import org.springframework.integration.dsl.MessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * The {@link MessageHandlerSpec} for {@link CamelMessageHandler}. - * - * @author Artem Bilan - * - * @since 6.0 - */ -public class CamelMessageHandlerSpec extends - MessageHandlerSpec { - - private String[] inboundHeaderNames = {"*"}; - - private String[] outboundHeaderNames = {"*"}; - - protected CamelMessageHandlerSpec(@Nullable ProducerTemplate producerTemplate) { - this.target = producerTemplate == null ? new CamelMessageHandler() : new CamelMessageHandler(producerTemplate); - } - - public CamelMessageHandlerSpec endpointUri(String endpointUri) { - this.target.setEndpointUri(endpointUri); - return this; - } - - public CamelMessageHandlerSpec endpointUri(Function, String> endpointUriFunction) { - return endpointUriExpression(new FunctionExpression<>(endpointUriFunction)); - } - - public CamelMessageHandlerSpec endpointUriExpression(String endpointUriExpression) { - return endpointUriExpression(PARSER.parseExpression(endpointUriExpression)); - } - - public CamelMessageHandlerSpec endpointUriExpression(Expression endpointUriExpression) { - this.target.setEndpointUriExpression(endpointUriExpression); - return this; - } - - protected CamelMessageHandlerSpec route(LambdaRouteBuilder route) { - this.target.setRoute(route); - return this; - } - - public CamelMessageHandlerSpec exchangePattern(ExchangePattern exchangePattern) { - this.target.setExchangePattern(exchangePattern); - return this; - } - - public CamelMessageHandlerSpec exchangePattern(Function, ExchangePattern> exchangePatternFunction) { - return exchangePatternExpression(new FunctionExpression<>(exchangePatternFunction)); - } - - public CamelMessageHandlerSpec exchangePatternExpression(String exchangePatternExpression) { - return exchangePatternExpression(PARSER.parseExpression(exchangePatternExpression)); - } - - public CamelMessageHandlerSpec exchangePatternExpression(Expression exchangePatternExpression) { - this.target.setExchangePatternExpression(exchangePatternExpression); - return this; - } - - public CamelMessageHandlerSpec inboundHeaderNames(String... inboundHeaderNames) { - Assert.notEmpty(inboundHeaderNames, "'inboundHeaderNames' must not be empty"); - this.inboundHeaderNames = Arrays.copyOf(inboundHeaderNames, inboundHeaderNames.length); - return addCamelHeaderMapper(); - } - - public CamelMessageHandlerSpec outboundHeaderNames(String... outboundHeaderNames) { - Assert.notEmpty(outboundHeaderNames, "'outboundHeaderNames' must not be empty"); - this.outboundHeaderNames = Arrays.copyOf(outboundHeaderNames, outboundHeaderNames.length); - return addCamelHeaderMapper(); - } - - private CamelMessageHandlerSpec addCamelHeaderMapper() { - CamelHeaderMapper headerMapper = new CamelHeaderMapper(); - headerMapper.setInboundHeaderNames(this.inboundHeaderNames); - headerMapper.setOutboundHeaderNames(this.outboundHeaderNames); - return headerMapper(headerMapper); - } - - public CamelMessageHandlerSpec headerMapper(HeaderMapper headerMapper) { - this.target.setHeaderMapper(headerMapper); - return this; - } - - public CamelMessageHandlerSpec exchangeProperties(Map exchangeProperties) { - this.target.setExchangeProperties(exchangeProperties); - return this; - } - - public CamelMessageHandlerSpec exchangePropertiesExpression(String exchangePropertiesExpression) { - return exchangePropertiesExpression(PARSER.parseExpression(exchangePropertiesExpression)); - } - - public CamelMessageHandlerSpec exchangePropertiesExpression(Expression exchangePropertiesExpression) { - this.target.setExchangePropertiesExpression(exchangePropertiesExpression); - return this; - } - -} diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/package-info.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/package-info.java deleted file mode 100644 index 9bd34666013..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/dsl/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Provides supporting classes for JavaDSL with Apache Camel components. - */ - -@org.jspecify.annotations.NullMarked -package org.springframework.integration.camel.dsl; diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/outbound/CamelMessageHandler.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/outbound/CamelMessageHandler.java deleted file mode 100644 index c4007890005..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/outbound/CamelMessageHandler.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.camel.outbound; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import org.apache.camel.CamelContext; -import org.apache.camel.CamelExecutionException; -import org.apache.camel.Endpoint; -import org.apache.camel.Exchange; -import org.apache.camel.ExchangePattern; -import org.apache.camel.ProducerTemplate; -import org.apache.camel.builder.LambdaRouteBuilder; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.model.RouteDefinition; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanInitializationException; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.camel.support.CamelHeaderMapper; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A {@link org.springframework.messaging.MessageHandler} for calling Apache Camel route - * and produce (optionally) a reply. - *

- * In the async mode, the {@link ProducerTemplate#asyncSend(Endpoint, Exchange)} is used. - *

- * The request-reply behavior can be controlled via {@link ExchangePattern} configuration - * or per message. By default, this handler works in an {@link ExchangePattern#InOnly} mode. - *

- * A default "mapping all headers" between Spring Integration and Apache Camel messages behavior - * can be customized via {@link #setHeaderMapper(HeaderMapper)} option. - *

- * The target Apache Camel endpoint to call can be determined by the {@link #endpointUriExpression}. - * By default, a {@link ProducerTemplate#getDefaultEndpoint()} is used. - * - * @author Artem Bilan - * - * @since 6.0 - * - * @see CamelHeaderMapper - */ -public class CamelMessageHandler extends AbstractReplyProducingMessageHandler { - - @SuppressWarnings("NullAway.Init") - private ProducerTemplate producerTemplate; - - private Expression exchangePatternExpression = new ValueExpression<>(ExchangePattern.InOnly); - - @Nullable - private Expression endpointUriExpression; - - @Nullable - private LambdaRouteBuilder route; - - private HeaderMapper headerMapper = new CamelHeaderMapper(); - - @Nullable - private Expression exchangePropertiesExpression; - - @SuppressWarnings("NullAway.Init") - private StandardEvaluationContext evaluationContext; - - public CamelMessageHandler() { - } - - public CamelMessageHandler(ProducerTemplate producerTemplate) { - Assert.notNull(producerTemplate, "'producerTemplate' must not be null"); - this.producerTemplate = producerTemplate; - } - - /** - * Set Camel route endpoint uri to send a message. - * Mutually exclusive with {@link #setEndpointUriExpression(Expression)} and {@link #setRoute(LambdaRouteBuilder)}. - * @param endpointUri the Camel route endpoint to send a message. - */ - public void setEndpointUri(String endpointUri) { - Assert.hasText(endpointUri, "'endpointUri' must not be empty"); - setEndpointUriExpression(new LiteralExpression(endpointUri)); - } - - /** - * Set Camel route endpoint uri to send a message. - * Mutually exclusive with {@link #setEndpointUri(String)} and {@link #setRoute(LambdaRouteBuilder)}. - * @param endpointUriExpression the SpEL expression to determine a Camel route endpoint to send a message. - */ - public void setEndpointUriExpression(Expression endpointUriExpression) { - Assert.notNull(endpointUriExpression, "'endpointUriExpression' must not be null"); - this.endpointUriExpression = endpointUriExpression; - } - - /** - * Set a {@link LambdaRouteBuilder} to add an inline Camel route definition. - * Can be used as a lambda {@code rb -> rb.from("direct:inbound").bean(MyBean.class)} - * or reference to external instance. - * Mutually exclusive with {@link #setEndpointUri(String)} and {@link #setEndpointUriExpression(Expression)}. - * The endpoint to send a message is extracted from the target {@link RouteBuilder}. - * @param route the {@link LambdaRouteBuilder} to use. - */ - public void setRoute(LambdaRouteBuilder route) { - Assert.notNull(route, "'route' must not be null"); - this.route = route; - } - - public void setExchangePattern(ExchangePattern exchangePattern) { - Assert.notNull(exchangePattern, "'exchangePattern' must not be null"); - setExchangePatternExpression(new ValueExpression<>(exchangePattern)); - } - - public void setExchangePatternExpression(Expression exchangePatternExpression) { - Assert.notNull(exchangePatternExpression, "'exchangePatternExpression' must not be null"); - this.exchangePatternExpression = exchangePatternExpression; - } - - /** - * Set a {@link HeaderMapper} to map request message headers into Apache Camel message headers and - * back if request-reply exchange pattern is used. - * @param headerMapper the {@link HeaderMapper} to use. - */ - public void setHeaderMapper(HeaderMapper headerMapper) { - Assert.notNull(headerMapper, "'headerMapper' must not be null"); - this.headerMapper = headerMapper; - } - - public void setExchangeProperties(Map exchangeProperties) { - Assert.notNull(exchangeProperties, "'exchangeProperties' must not be null"); - setExchangePropertiesExpression(new ValueExpression<>(exchangeProperties)); - } - - /** - * Set a SpEL expression to evaluate {@link org.apache.camel.Exchange} properties as a {@link Map}. - * @param exchangePropertiesExpression the expression for exchange properties. - */ - public void setExchangePropertiesExpression(Expression exchangePropertiesExpression) { - this.exchangePropertiesExpression = exchangePropertiesExpression; - } - - @Override - protected final void doInit() { - Assert.state(this.endpointUriExpression == null || this.route == null, - "The 'endpointUri' option is mutually exclusive with 'route'"); - - BeanFactory beanFactory = getBeanFactory(); - if (this.producerTemplate == null) { // NOSONAR - this.producerTemplate = beanFactory.getBean(CamelContext.class).createProducerTemplate(); - } - - LambdaRouteBuilder lambdaRouteBuilder = this.route; - if (lambdaRouteBuilder != null) { - CamelContext camelContext = this.producerTemplate.getCamelContext(); - RouteBuilder routeBuilder = - new RouteBuilder(camelContext) { - - @Override - public void configure() throws Exception { - lambdaRouteBuilder.accept(this); - } - - }; - - try { - camelContext.addRoutes(routeBuilder); - } - catch (Exception ex) { - throw new BeanInitializationException("Cannot load Camel route", ex); - } - - RouteDefinition routeDefinition = routeBuilder.getRouteCollection().getRoutes().get(0); - this.endpointUriExpression = new LiteralExpression(routeDefinition.getInput().getEndpointUri()); - } - - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(beanFactory); - } - - @Override - @Nullable - protected Object handleRequestMessage(Message requestMessage) { - ExchangePattern exchangePattern = - this.exchangePatternExpression.getValue(this.evaluationContext, requestMessage, ExchangePattern.class); - - Assert.notNull(exchangePattern, "'exchangePatternExpression' must not evaluate to null"); - - Endpoint endpoint = resolveEndpoint(requestMessage); - Exchange exchange = prepareInExchange(endpoint, exchangePattern, requestMessage); - - if (isAsync()) { - CompletableFuture result = this.producerTemplate.asyncSend(endpoint, exchange); - return result.thenApply(resultExchange -> buildReply(exchangePattern, resultExchange)); - } - else { - Exchange result = this.producerTemplate.send(endpoint, exchange); - return buildReply(exchangePattern, result); - } - } - - private Endpoint resolveEndpoint(Message requestMessage) { - String endpointUri = - this.endpointUriExpression != null - ? this.endpointUriExpression.getValue(this.evaluationContext, requestMessage, String.class) - : null; - - if (StringUtils.hasText(endpointUri)) { - return this.producerTemplate.getCamelContext().getEndpoint(endpointUri); - } - else { - return this.producerTemplate.getDefaultEndpoint(); - } - } - - @SuppressWarnings("unchecked") - private Exchange prepareInExchange(Endpoint endpoint, ExchangePattern exchangePattern, Message requestMessage) { - Exchange exchange = endpoint.createExchange(exchangePattern); - - Map exchangeProperties = - this.exchangePropertiesExpression != null - ? this.exchangePropertiesExpression.getValue(this.evaluationContext, requestMessage, Map.class) - : null; - - if (exchangeProperties != null) { - for (Map.Entry property : exchangeProperties.entrySet()) { - exchange.setProperty(property.getKey(), property.getValue()); - } - } - org.apache.camel.Message in = exchange.getIn(); - this.headerMapper.fromHeaders(requestMessage.getHeaders(), in); - in.setBody(requestMessage.getPayload()); - return exchange; - } - - @Nullable - private AbstractIntegrationMessageBuilder buildReply(ExchangePattern exchangePattern, Exchange result) { - if (result.isFailed()) { - throw CamelExecutionException.wrapCamelExecutionException(result, result.getException()); - } - if (exchangePattern.isOutCapable()) { - org.apache.camel.Message out = result.getMessage(); - return getMessageBuilderFactory() - .withPayload(out.getBody()) - .copyHeaders(this.headerMapper.toHeaders(out)); - } - else { - return null; - } - } - -} diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/outbound/package-info.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/outbound/package-info.java deleted file mode 100644 index 19ea69d3d21..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/outbound/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Provides classes for Apache Camel outbound channel adapters. - */ - -@org.jspecify.annotations.NullMarked -package org.springframework.integration.camel.outbound; diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/support/CamelHeaderMapper.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/support/CamelHeaderMapper.java deleted file mode 100644 index 7d6abcd3ffc..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/support/CamelHeaderMapper.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.camel.support; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.apache.camel.Message; - -import org.springframework.core.log.LogAccessor; -import org.springframework.core.log.LogMessage; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.Assert; -import org.springframework.util.PatternMatchUtils; - -/** - * A {@link HeaderMapper} for mapping headers from Spring Integration message - * to Apache Camel message and back. - * - * @author Artem Bilan - * - * @since 6.0 - */ -public class CamelHeaderMapper implements HeaderMapper { - - private static final LogAccessor LOGGER = new LogAccessor(CamelHeaderMapper.class); - - private String[] inboundHeaderNames = {"*"}; - - private String[] outboundHeaderNames = {"*"}; - - /** - * Provide a list of patterns to map Apache Camel message headers into Spring Integration message. - * By default, it maps all. - * @param inboundHeaderNames the Apache Camel message headers patterns to map. - */ - public void setInboundHeaderNames(String... inboundHeaderNames) { - Assert.notNull(inboundHeaderNames, "'inboundHeaderNames' must not be null"); - String[] copy = Arrays.copyOf(inboundHeaderNames, inboundHeaderNames.length); - Arrays.sort(copy); - this.inboundHeaderNames = copy; - } - - /** - * Provide a list of patterns to map Spring Integration message headers into an Apache Camel message. - * By default, it maps all. - * @param outboundHeaderNames the header patterns to map. - */ - public void setOutboundHeaderNames(String... outboundHeaderNames) { - Assert.notNull(outboundHeaderNames, "'outboundHeaderNames' must not be null"); - String[] copy = Arrays.copyOf(outboundHeaderNames, outboundHeaderNames.length); - Arrays.sort(copy); - this.outboundHeaderNames = copy; - } - - @Override - public void fromHeaders(MessageHeaders headers, Message target) { - for (Map.Entry entry : headers.entrySet()) { - String name = entry.getKey(); - if (shouldMapHeader(name, this.outboundHeaderNames)) { - Object value = entry.getValue(); - if (value != null) { - target.setHeader(name, value); - } - } - } - } - - @Override - public Map toHeaders(Message source) { - Map headers = new HashMap<>(); - for (Map.Entry entry : source.getHeaders().entrySet()) { - String name = entry.getKey(); - if (shouldMapHeader(name, this.inboundHeaderNames)) { - Object value = entry.getValue(); - if (value != null) { - headers.put(name, value); - } - } - } - return headers; - } - - private static boolean shouldMapHeader(String headerName, String[] patterns) { - if (patterns.length > 0) { - for (String pattern : patterns) { - if (PatternMatchUtils.simpleMatch(pattern, headerName)) { - LOGGER.debug(LogMessage.format("headerName=[{0}] WILL be mapped, matched pattern={1}", - headerName, pattern)); - return true; - } - } - } - LOGGER.debug(LogMessage.format("headerName=[{0}] WILL NOT be mapped", headerName)); - return false; - } - -} diff --git a/spring-integration-camel/src/main/java/org/springframework/integration/camel/support/package-info.java b/spring-integration-camel/src/main/java/org/springframework/integration/camel/support/package-info.java deleted file mode 100644 index bed359ce739..00000000000 --- a/spring-integration-camel/src/main/java/org/springframework/integration/camel/support/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Provides supporting classes for Apache Camel channel adapters. - */ - -@org.jspecify.annotations.NullMarked -package org.springframework.integration.camel.support; diff --git a/spring-integration-camel/src/test/java/org/springframework/integration/camel/dsl/CamelDslTests.java b/spring-integration-camel/src/test/java/org/springframework/integration/camel/dsl/CamelDslTests.java deleted file mode 100644 index 8f970b5a6c8..00000000000 --- a/spring-integration-camel/src/test/java/org/springframework/integration/camel/dsl/CamelDslTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.camel.dsl; - -import org.apache.camel.CamelContext; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.spring.SpringCamelContext; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.core.MessagingTemplate; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * @author Artem Bilan - * - * @since 6.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class CamelDslTests { - - @Autowired - @Qualifier("camelFlow.input") - MessageChannel input; - - @Test - void sendAndReceiveCamelRoute() { - MessagingTemplate messagingTemplate = new MessagingTemplate(); - messagingTemplate.setBeanFactory(mock()); - String result = messagingTemplate.convertSendAndReceive(this.input, "apache camel", String.class); - assertThat(result).isEqualTo("___APACHE CAMEL___"); - } - - @Configuration - @EnableIntegration - public static class Config { - - @Bean - CamelContext springCamelContext() { - return new SpringCamelContext(); - } - - @EventListener(ContextRefreshedEvent.class) - public void simpleRoute() throws Exception { - RouteBuilder.addRoutes(springCamelContext(), - rb -> rb.from("direct:simple").bean("camelDslTests.Config", "transformPayload")); - } - - @Bean - IntegrationFlow camelFlow() { - return f -> f - .handle(Camel.gateway().endpointUri("direct:simple")) - .handle(Camel.route(this::camelRoute)); - } - - private void camelRoute(RouteBuilder routeBuilder) { - routeBuilder.from("direct:inbound").transform(routeBuilder.simple("${body.toUpperCase()}")); - } - - public String transformPayload(String payload) { - return "___" + payload + "___"; - } - - } - -} diff --git a/spring-integration-camel/src/test/java/org/springframework/integration/camel/outbound/CamelMessageHandlerTests.java b/spring-integration-camel/src/test/java/org/springframework/integration/camel/outbound/CamelMessageHandlerTests.java deleted file mode 100644 index da4bdc23e60..00000000000 --- a/spring-integration-camel/src/test/java/org/springframework/integration/camel/outbound/CamelMessageHandlerTests.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.camel.outbound; - -import org.apache.camel.CamelExecutionException; -import org.apache.camel.ExchangePattern; -import org.apache.camel.ProducerTemplate; -import org.apache.camel.RoutesBuilder; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.mock.MockEndpoint; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.Test; - -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.camel.support.CamelHeaderMapper; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 6.0 - */ -public class CamelMessageHandlerTests extends CamelTestSupport implements TestApplicationContextAware { - - @Test - void inOnlyPatternSyncMessageHandler() throws InterruptedException { - Message messageUnderTest = new GenericMessage<>("Hello Camel!"); - Message messageUnderTest2 = new GenericMessage<>("Hello Camel again!"); - - MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); - mockEndpoint.message(0).body().isEqualTo(messageUnderTest.getPayload()); - mockEndpoint.message(0).header(MessageHeaders.ID).isEqualTo(messageUnderTest.getHeaders().getId()); - mockEndpoint.message(1).body().isEqualTo(messageUnderTest2.getPayload()); - mockEndpoint.message(1).header(MessageHeaders.ID).isEqualTo(messageUnderTest2.getHeaders().getId()); - - CamelMessageHandler camelMessageHandler = new CamelMessageHandler(template()); - camelMessageHandler.setEndpointUri("direct:simple"); - camelMessageHandler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - camelMessageHandler.afterPropertiesSet(); - - camelMessageHandler.handleMessage(messageUnderTest); - camelMessageHandler.handleMessage(messageUnderTest2); - - mockEndpoint.assertIsSatisfied(); - } - - @Test - void inOutPatternSyncMessageHandlerWithNoRequestHeadersButReplyHeaders() throws InterruptedException { - SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); - QueueChannel replyChannel = new QueueChannel(); - Message messageUnderTest = - MessageBuilder.withPayload("test data") - .setHeader("exchangePattern", "InOut") - .setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel) - .build(); - - MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); - mockEndpoint.expectedHeaderReceived(MessageHeaders.ID, null); - mockEndpoint.expectedHeaderReceived(MessageHeaders.TIMESTAMP, null); - mockEndpoint.whenAnyExchangeReceived(exchange -> { - org.apache.camel.Message out = exchange.getMessage(); - out.setBody("Reply for: " + exchange.getIn().getBody()); - out.setHeader("testHeader", "testHeaderValue"); - out.setHeader("notMappedHeader", "someValue"); - }); - - CamelHeaderMapper headerMapper = new CamelHeaderMapper(); - headerMapper.setOutboundHeaderNames(""); - headerMapper.setInboundHeaderNames("testHeader"); - - CamelMessageHandler camelMessageHandler = new CamelMessageHandler(template()); - camelMessageHandler.setEndpointUriExpression(new FunctionExpression<>(m -> "direct:simple")); - camelMessageHandler.setExchangePatternExpression(spelExpressionParser.parseExpression("headers.exchangePattern")); - camelMessageHandler.setHeaderMapper(headerMapper); - camelMessageHandler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - camelMessageHandler.afterPropertiesSet(); - - camelMessageHandler.handleMessage(messageUnderTest); - - Message receive = replyChannel.receive(10_000); - - mockEndpoint.assertIsSatisfied(); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("Reply for: test data"); - assertThat(receive.getHeaders()) - .containsEntry("testHeader", "testHeaderValue") - .doesNotContainKey("notMappedHeader"); - } - - @Test - void inOnlyPatternAsyncMessageHandlerWithException() throws InterruptedException { - QueueChannel errorChannel = new QueueChannel(); - Message messageUnderTest = - MessageBuilder.withPayload("test data") - .setHeader(MessageHeaders.ERROR_CHANNEL, errorChannel) - .build(); - - MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); - mockEndpoint.whenAnyExchangeReceived(exchange -> { - throw new RuntimeException("intentional"); - }); - - CamelMessageHandler camelMessageHandler = new CamelMessageHandler(template()); - camelMessageHandler.setEndpointUri("direct:simple"); - camelMessageHandler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - camelMessageHandler.setAsync(true); - camelMessageHandler.afterPropertiesSet(); - - camelMessageHandler.handleMessage(messageUnderTest); - Message receive = errorChannel.receive(10_000); - - mockEndpoint.assertIsSatisfied(); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()) - .asInstanceOf(InstanceOfAssertFactories.throwable(MessageHandlingException.class)) - .hasCauseInstanceOf(CamelExecutionException.class) - .hasRootCauseExactlyInstanceOf(RuntimeException.class) - .hasStackTraceContaining("intentional"); - } - - @Test - void inOutPatternAsyncMessageHandler() throws InterruptedException { - QueueChannel replyChannel = new QueueChannel(); - Message messageUnderTest = - MessageBuilder.withPayload("test async data") - .setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel) - .build(); - - MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); - mockEndpoint.whenAnyExchangeReceived(exchange -> - exchange.getMessage().setBody("Async reply for: " + exchange.getIn().getBody())); - - ProducerTemplate producerTemplate = template(); - producerTemplate.setDefaultEndpointUri("direct:simple"); - CamelMessageHandler camelMessageHandler = new CamelMessageHandler(producerTemplate); - camelMessageHandler.setExchangePattern(ExchangePattern.InOut); - camelMessageHandler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - camelMessageHandler.setAsync(true); - camelMessageHandler.afterPropertiesSet(); - - camelMessageHandler.handleMessage(messageUnderTest); - - Message receive = replyChannel.receive(10_000); - - mockEndpoint.assertIsSatisfied(); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("Async reply for: test async data"); - } - - @Override - protected RoutesBuilder createRouteBuilder() { - return new RouteBuilder() { - - @Override - public void configure() { - from("direct:simple").to("mock:result"); - } - - }; - } - -} diff --git a/spring-integration-camel/src/test/resources/log4j2-test.xml b/spring-integration-camel/src/test/resources/log4j2-test.xml deleted file mode 100644 index 32403b74b53..00000000000 --- a/spring-integration-camel/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraNamespaceHandler.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraNamespaceHandler.java deleted file mode 100644 index ad2b8a7b68b..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraNamespaceHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.config.xml; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * The namespace handler for "int-cassandra" namespace. - * - * @author Artem Bilan - * @author Filippo Balicchia - * - * @since 6.0 - */ -public class CassandraNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - @Override - public void init() { - registerBeanDefinitionParser("outbound-channel-adapter", new CassandraOutboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-gateway", new CassandraOutboundGatewayParser()); - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraOutboundChannelAdapterParser.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraOutboundChannelAdapterParser.java deleted file mode 100644 index 50ca258e69e..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraOutboundChannelAdapterParser.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.cassandra.outbound.CassandraMessageHandler; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; - -/** - * The parser for the {@code }. - * - * @author Filippo Balicchia - * @author Artem Bilan - * - * @since 6.0 - */ -public class CassandraOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CassandraMessageHandler.class); - builder.addPropertyValue("producesReply", false); - CassandraParserUtils.processOutboundTypeAttributes(element, parserContext, builder); - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraOutboundGatewayParser.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraOutboundGatewayParser.java deleted file mode 100644 index 88c77b9c7aa..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraOutboundGatewayParser.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.cassandra.outbound.CassandraMessageHandler; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; - -/** - * The parser for the {@code }. - * - * @author Filippo Balicchia - * @author Artem Bilan - * - * @since 6.0 - */ -public class CassandraOutboundGatewayParser extends AbstractConsumerEndpointParser { - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CassandraMessageHandler.class); - builder.addPropertyValue("producesReply", true); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); - CassandraParserUtils.processOutboundTypeAttributes(element, parserContext, builder); - return builder; - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraParserUtils.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraParserUtils.java deleted file mode 100644 index 11bc0f54e19..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/CassandraParserUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.config.xml; - -import java.util.List; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * The {@code int-cassandra} namespace XML parser helper. - * - * @author Filippo Balicchia - * @author Artem Bilan - * - * @since 6.0 - */ -public final class CassandraParserUtils { - - public static void processOutboundTypeAttributes(Element element, ParserContext parserContext, - BeanDefinitionBuilder builder) { - - String cassandraTemplate = element.getAttribute("cassandra-template"); - String mode = element.getAttribute("mode"); - String ingestQuery = element.getAttribute("ingest-query"); - String query = element.getAttribute("query"); - - if (!StringUtils.hasText(cassandraTemplate)) { - parserContext.getReaderContext().error("cassandra-template is required", element); - } - - builder.addConstructorArgReference(cassandraTemplate); - if (StringUtils.hasText(mode)) { - builder.addConstructorArgValue(mode); - } - - BeanDefinition statementExpressionDef = IntegrationNamespaceUtils - .createExpressionDefIfAttributeDefined("statement-expression", element); - - if (statementExpressionDef != null) { - builder.addPropertyValue("statementExpression", statementExpressionDef); - } - - if (!areMutuallyExclusive(query, statementExpressionDef, ingestQuery)) { - parserContext.getReaderContext() - .error("'query', 'ingest-query', 'statement-expression' are mutually exclusive", element); - } - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "write-options"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ingest-query"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "query"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async"); - - List parameterExpressions = DomUtils.getChildElementsByTagName(element, "parameter-expression"); - if (!CollectionUtils.isEmpty(parameterExpressions)) { - ManagedMap parameterExpressionsMap = new ManagedMap<>(); - for (Element parameterExpressionElement : parameterExpressions) { - String name = parameterExpressionElement.getAttribute(AbstractBeanDefinitionParser.NAME_ATTRIBUTE); - BeanDefinition expression = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined( - IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE, parameterExpressionElement); - if (expression != null) { - parameterExpressionsMap.put(name, expression); - } - } - builder.addPropertyValue("parameterExpressions", parameterExpressionsMap); - } - - } - - private static boolean areMutuallyExclusive(String query, @Nullable BeanDefinition statementExpressionDef, - String ingestQuery) { - - return !StringUtils.hasText(query) && statementExpressionDef == null && !StringUtils.hasText(ingestQuery) - || !(StringUtils.hasText(query) && statementExpressionDef != null && StringUtils.hasText(ingestQuery)) - && (StringUtils.hasText(query) ^ statementExpressionDef != null) ^ StringUtils.hasText(ingestQuery); - } - - private CassandraParserUtils() { - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/package-info.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/package-info.java deleted file mode 100644 index 90866452357..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/config/xml/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Provides classes for Cassandra parsers and namespace handlers. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.cassandra.config.xml; diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/Cassandra.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/Cassandra.java deleted file mode 100644 index bde5864d2c5..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/Cassandra.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.dsl; - -import org.springframework.data.cassandra.core.ReactiveCassandraOperations; -import org.springframework.integration.cassandra.outbound.CassandraMessageHandler; - -/** - * Factory class for Apache Cassandra components DSL. - * - * @author Artem Bilan - * - * @since 6.0 - */ -public final class Cassandra { - - /** - * Create an instance of {@link CassandraMessageHandlerSpec} for the provided {@link ReactiveCassandraOperations}. - * @param cassandraOperations the {@link ReactiveCassandraOperations} to use. - * @return the spec. - */ - public static CassandraMessageHandlerSpec outboundChannelAdapter(ReactiveCassandraOperations cassandraOperations) { - return new CassandraMessageHandlerSpec(cassandraOperations); - } - - /** - * Create an instance of {@link CassandraMessageHandlerSpec} for the provided {@link ReactiveCassandraOperations}. - * @param cassandraOperations the {@link ReactiveCassandraOperations} to use. - * @param queryType the {@link CassandraMessageHandler.Type} to use. - * @return the spec. - */ - public static CassandraMessageHandlerSpec outboundChannelAdapter(ReactiveCassandraOperations cassandraOperations, - CassandraMessageHandler.Type queryType) { - - return new CassandraMessageHandlerSpec(cassandraOperations, queryType); - } - - /** - * Create an instance of {@link CassandraMessageHandlerSpec} for the provided {@link ReactiveCassandraOperations} - * in an outbound gateway mode. - * @param cassandraOperations the {@link ReactiveCassandraOperations} to use. - * @return the spec. - */ - public static CassandraMessageHandlerSpec outboundGateway(ReactiveCassandraOperations cassandraOperations) { - return new CassandraMessageHandlerSpec(cassandraOperations) - .producesReply(true); - } - - /** - * Create an instance of {@link CassandraMessageHandlerSpec} for the provided {@link ReactiveCassandraOperations} - * in an outbound gateway mode. - * @param cassandraOperations the {@link ReactiveCassandraOperations} to use. - * @param queryType the {@link CassandraMessageHandler.Type} to use. - * @return the spec. - */ - public static CassandraMessageHandlerSpec outboundGateway(ReactiveCassandraOperations cassandraOperations, - CassandraMessageHandler.Type queryType) { - - return new CassandraMessageHandlerSpec(cassandraOperations, queryType) - .producesReply(true); - } - - private Cassandra() { - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/CassandraMessageHandlerSpec.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/CassandraMessageHandlerSpec.java deleted file mode 100644 index 7a3cc762ccd..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/CassandraMessageHandlerSpec.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.dsl; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import com.datastax.oss.driver.api.core.cql.Statement; - -import org.springframework.data.cassandra.core.ReactiveCassandraOperations; -import org.springframework.data.cassandra.core.cql.WriteOptions; -import org.springframework.expression.Expression; -import org.springframework.integration.cassandra.outbound.CassandraMessageHandler; -import org.springframework.integration.dsl.MessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.messaging.Message; - -/** - * The {@link MessageHandlerSpec} for {@link CassandraMessageHandler}. - * - * @author Artem Bilan - * - * @since 6.0 - */ -public class CassandraMessageHandlerSpec extends - MessageHandlerSpec { - - private final Map parameterExpressions = new HashMap<>(); - - protected CassandraMessageHandlerSpec(ReactiveCassandraOperations cassandraOperations) { - this.target = new CassandraMessageHandler(cassandraOperations); - } - - protected CassandraMessageHandlerSpec(ReactiveCassandraOperations cassandraOperations, - CassandraMessageHandler.Type queryType) { - - this.target = new CassandraMessageHandler(cassandraOperations, queryType); - } - - protected CassandraMessageHandlerSpec producesReply(boolean producesReply) { - this.target.setProducesReply(producesReply); - return _this(); - } - - /** - * Set an ingest query. - * @param ingestQuery ingest query to use. - * @return this spec - */ - public CassandraMessageHandlerSpec ingestQuery(String ingestQuery) { - this.target.setIngestQuery(ingestQuery); - return _this(); - } - - /** - * Set a {@link WriteOptions} for {@code INSERT}, {@code UPDATE} or {@code DELETE} operations. - * @param writeOptions the {@link WriteOptions} to use. - * @return this spec - */ - public CassandraMessageHandlerSpec writeOptions(WriteOptions writeOptions) { - this.target.setWriteOptions(writeOptions); - return _this(); - } - - /** - * Set a SpEL expression to evaluate a {@link Statement} against request message. - * @param statementExpression the SpEL expression to use. - * @return this spec - */ - public CassandraMessageHandlerSpec statementExpression(String statementExpression) { - return statementExpression(PARSER.parseExpression(statementExpression)); - } - - /** - * Set a SpEL expression to evaluate a {@link Statement} against request message. - * @param statementExpression the SpEL expression to use. - * @return this spec - */ - public CassandraMessageHandlerSpec statementExpression(Expression statementExpression) { - this.target.setStatementExpression(statementExpression); - return _this(); - } - - /** - * Set a {@link Function} to evaluate a {@link Statement} against request message. - * @param statementFunction the function to use. - * @return this spec - */ - public CassandraMessageHandlerSpec statementFunction(Function, Statement> statementFunction) { - this.target.setStatementProcessor(statementFunction::apply); - return _this(); - } - - /** - * Set a {@code SELECT} query. - * @param query the CQL query to execute - * @return this spec - */ - public CassandraMessageHandlerSpec query(String query) { - this.target.setQuery(query); - return _this(); - } - - /** - * Set a map for named parameters and expressions for their values against a request message. - * @param parameterExpressions the map to use. - * @return this spec - */ - public CassandraMessageHandlerSpec parameterExpressions(Map parameterExpressions) { - this.target.setParameterExpressions(parameterExpressions); - return _this(); - } - - /** - * Add a named bindable parameter with a SpEL expression to evaluate its value against a request message. - * @param name the name of parameter. - * @param expression the SpEL expression for parameter value. - * @return this spec - */ - public CassandraMessageHandlerSpec parameter(String name, String expression) { - return parameter(name, PARSER.parseExpression(expression)); - } - - /** - * Add a named bindable parameter with a function to evaluate its value against a request message. - * @param name the name of parameter. - * @param function the function for parameter value. - * @return this spec - */ - public CassandraMessageHandlerSpec parameter(String name, Function, ?> function) { - return parameter(name, new FunctionExpression<>(function)); - } - - /** - * Add a named bindable parameter with a SpEL expression to evaluate its value against a request message. - * @param name the name of parameter. - * @param expression the SpEL expression for parameter value. - * @return this spec - */ - public CassandraMessageHandlerSpec parameter(String name, Expression expression) { - this.parameterExpressions.put(name, expression); - return parameterExpressions(this.parameterExpressions); - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/package-info.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/package-info.java deleted file mode 100644 index 4bd41357f00..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides Apache Cassandra Components support for the Java DSL. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.cassandra.dsl; diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java deleted file mode 100644 index 1f239ae6fde..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandler.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.outbound; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.StreamSupport; - -import com.datastax.oss.driver.api.core.DriverException; -import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; -import com.datastax.oss.driver.api.core.cql.BatchType; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.querybuilder.QueryBuilder; -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.dao.DataAccessException; -import org.springframework.data.cassandra.ReactiveResultSet; -import org.springframework.data.cassandra.ReactiveSession; -import org.springframework.data.cassandra.core.InsertOptions; -import org.springframework.data.cassandra.core.ReactiveCassandraOperations; -import org.springframework.data.cassandra.core.UpdateOptions; -import org.springframework.data.cassandra.core.WriteResult; -import org.springframework.data.cassandra.core.cql.QueryOptionsUtil; -import org.springframework.data.cassandra.core.cql.ReactiveSessionCallback; -import org.springframework.data.cassandra.core.cql.WriteOptions; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.expression.spel.support.StandardTypeLocator; -import org.springframework.integration.expression.ExpressionEvalMap; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * An {@link AbstractReplyProducingMessageHandler} implementation for Cassandra outbound operations. - * - * @author Soby Chacko - * @author Artem Bilan - * @author Filippo Balicchia - * @author Ngoc Nhan - * - * @since 6.0 - */ -public class CassandraMessageHandler extends AbstractReplyProducingMessageHandler { - - private final Map parameterExpressions = new HashMap<>(); - - private Type mode; - - private final ReactiveCassandraOperations cassandraOperations; - - private boolean producesReply; - - /** - * Prepared statement to use in association with high throughput ingestion. - */ - @Nullable - private String ingestQuery; - - /** - * Various options that can be used for Cassandra writes. - */ - private WriteOptions writeOptions; - - @SuppressWarnings("NullAway.Init") - private ReactiveSessionMessageCallback sessionMessageCallback; - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - public CassandraMessageHandler(ReactiveCassandraOperations cassandraOperations) { - this(cassandraOperations, Type.INSERT); - } - - @SuppressWarnings("this-escape") - public CassandraMessageHandler(ReactiveCassandraOperations cassandraOperations, - CassandraMessageHandler.Type queryType) { - - Assert.notNull(cassandraOperations, "'cassandraOperations' must not be null."); - Assert.notNull(queryType, "'queryType' must not be null."); - this.cassandraOperations = cassandraOperations; // NOSONAR - this.mode = queryType; - setAsync(true); - this.writeOptions = - switch (this.mode) { - case INSERT -> InsertOptions.empty(); - case UPDATE -> UpdateOptions.empty(); - case DELETE, STATEMENT -> WriteOptions.empty(); - }; - } - - public void setIngestQuery(String ingestQuery) { - Assert.hasText(ingestQuery, "'ingestQuery' must not be empty"); - this.ingestQuery = ingestQuery; - this.mode = Type.INSERT; - } - - public void setWriteOptions(WriteOptions writeOptions) { - Assert.notNull(writeOptions, "'writeOptions' must not be null"); - this.writeOptions = writeOptions; - } - - public void setProducesReply(boolean producesReply) { - this.producesReply = producesReply; - } - - public void setStatementExpressionString(String statementExpression) { - setStatementExpression(EXPRESSION_PARSER.parseExpression(statementExpression)); - } - - @SuppressWarnings("unchecked") - public void setStatementExpression(Expression statementExpression) { - ExpressionEvaluatingMessageProcessor expressionEvaluatingMessageProcessor = - new ExpressionEvaluatingMessageProcessor<>(statementExpression, Statement.class) { - - @Override - protected StandardEvaluationContext getEvaluationContext() { - return (StandardEvaluationContext) CassandraMessageHandler.this.evaluationContext; - } - - }; - setStatementProcessor((ExpressionEvaluatingMessageProcessor>) expressionEvaluatingMessageProcessor); - } - - @SuppressWarnings("NullAway") // Cassandra driver uses NullAllowingImmutableMap - public void setQuery(String query) { - Assert.hasText(query, "'query' must not be empty"); - this.sessionMessageCallback = - (session, requestMessage) -> - session.execute(query, - ExpressionEvalMap.from(this.parameterExpressions) - .usingEvaluationContext(this.evaluationContext) - .withRoot(requestMessage) - .build()); - this.mode = Type.STATEMENT; - } - - public void setParameterExpressions(Map parameterExpressions) { - Assert.notEmpty(parameterExpressions, "'parameterExpressions' must not be empty."); - this.parameterExpressions.clear(); - this.parameterExpressions.putAll(parameterExpressions); - } - - public void setStatementProcessor(MessageProcessor> statementProcessor) { - Assert.notNull(statementProcessor, "'statementProcessor' must not be null."); - this.sessionMessageCallback = - (session, requestMessage) -> { - Statement statement = statementProcessor.processMessage(requestMessage); - Assert.notNull(statement, "Statement must not be null"); - return session.execute( - QueryOptionsUtil.addQueryOptions(statement, - this.writeOptions)); - }; - this.mode = Type.STATEMENT; - } - - @Override - public String getComponentType() { - return "cassandra:outbound-" + (this.producesReply ? "gateway" : "channel-adapter"); - } - - @Override - protected void doInit() { - super.doInit(); - - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - TypeLocator typeLocator = this.evaluationContext.getTypeLocator(); - if (typeLocator instanceof StandardTypeLocator standardTypeLocator) { - /* - * Register the Cassandra Query DSL package, so they don't need a FQCN for QueryBuilder, for example. - */ - standardTypeLocator.registerImport(QueryBuilder.class.getPackage().getName()); - } - } - - @Override - @Nullable - protected Object handleRequestMessage(Message requestMessage) { - Object payload = requestMessage.getPayload(); - - Type modeToUse = payload instanceof Statement ? Type.STATEMENT : this.mode; - - Mono result = - switch (modeToUse) { - case INSERT -> handleInsert(payload); - case UPDATE -> handleUpdate(payload); - case DELETE -> handleDelete(payload); - case STATEMENT -> handleStatement(requestMessage); - }; - - if (this.producesReply) { - return isAsync() ? result : result.block(); - } - else { - if (isAsync()) { - result.subscribe(); - } - else { - result.block(); - } - return null; - } - } - - @SuppressWarnings("unchecked") - private Mono handleInsert(Object payload) { - final String localIngestQuery = this.ingestQuery; - if (localIngestQuery != null) { - Assert.isInstanceOf(List.class, payload, - "to perform 'ingest' the 'payload' must be of 'List>' type."); - List list = (List) payload; - for (Object o : list) { - Assert.isInstanceOf(List.class, o, - "to perform 'ingest' the 'payload' must be of 'List>' type."); - } - List> rows = (List>) payload; - return this.cassandraOperations.getReactiveCqlOperations() - .execute((ReactiveSessionCallback) session -> - session.prepare( - QueryOptionsUtil.addQueryOptions(SimpleStatement.newInstance(localIngestQuery), - this.writeOptions)) - .flatMapMany(s -> - Flux.fromIterable(rows) - .map(row -> s.bind(row.toArray()))) - .collect(() -> new BatchStatementBuilder(BatchType.UNLOGGED), - BatchStatementBuilder::addStatement) - .map(BatchStatementBuilder::build) - .flatMap(session::execute) - .transform(this::transformToWriteResult)) - .next(); - } - else { - if (payload instanceof List) { - return this.cassandraOperations.batchOps() - .insert((List) payload, this.writeOptions) - .execute(); - } - else { - return this.cassandraOperations.insert(payload, (InsertOptions) this.writeOptions); - } - } - } - - private Mono handleUpdate(Object payload) { - if (payload instanceof List) { - return this.cassandraOperations.batchOps() - .update((List) payload, this.writeOptions) - .execute(); - } - else { - return this.cassandraOperations.update(payload, (UpdateOptions) this.writeOptions); - } - } - - private Mono handleDelete(Object payload) { - if (payload instanceof List) { - return this.cassandraOperations.batchOps() - .delete((List) payload) - .execute(); - } - else { - return this.cassandraOperations.delete(payload, this.writeOptions); - } - } - - private Mono handleStatement(Message requestMessage) { - Object payload = requestMessage.getPayload(); - Mono resultSetMono; - if (payload instanceof Statement) { - resultSetMono = this.cassandraOperations.getReactiveCqlOperations() - .queryForResultSet((Statement) payload); - } - else { - resultSetMono = this.cassandraOperations.getReactiveCqlOperations() - .execute((ReactiveSessionCallback) session -> - this.sessionMessageCallback.doInSession(session, requestMessage)) - .next(); - } - - return resultSetMono.transform(this::transformToWriteResult); - } - - private Mono transformToWriteResult(Mono resultSetMono) { - return resultSetMono.map(ReactiveWriteResult::new); - } - - /** - * The mode for the {@link CassandraMessageHandler}. - */ - public enum Type { - - /** - * Set a {@link CassandraMessageHandler} into an {@code insert} mode. - */ - INSERT, - - /** - * Set a {@link CassandraMessageHandler} into an {@code update} mode. - */ - UPDATE, - - /** - * Set a {@link CassandraMessageHandler} into a {@code delete} mode. - */ - DELETE, - - /** - * Set a {@link CassandraMessageHandler} into a {@code statement} mode. - */ - STATEMENT; - - } - - @FunctionalInterface - private interface ReactiveSessionMessageCallback { - - Mono doInSession(ReactiveSession session, Message requestMessage) - throws DriverException, DataAccessException; - - } - - private static final class ReactiveWriteResult extends WriteResult { - - ReactiveWriteResult(ReactiveResultSet reactiveResultSet) { - super(reactiveResultSet.getAllExecutionInfo(), - reactiveResultSet.wasApplied(), - StreamSupport.stream(reactiveResultSet.availableRows().toIterable().spliterator(), false).toList()); - } - - } - -} diff --git a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/package-info.java b/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/package-info.java deleted file mode 100644 index 44708825811..00000000000 --- a/spring-integration-cassandra/src/main/java/org/springframework/integration/cassandra/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting Cassandra outbound endpoints. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.cassandra.outbound; diff --git a/spring-integration-cassandra/src/main/resources/META-INF/spring.handlers b/spring-integration-cassandra/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index 48cebcf0835..00000000000 --- a/spring-integration-cassandra/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/cassandra=org.springframework.integration.cassandra.config.xml.CassandraNamespaceHandler diff --git a/spring-integration-cassandra/src/main/resources/META-INF/spring.schemas b/spring-integration-cassandra/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index 7ee584bf09f..00000000000 --- a/spring-integration-cassandra/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,3 +0,0 @@ -http\://www.springframework.org/schema/integration/cassandra/spring-integration-cassandra.xsd=org/springframework/integration/config/xml/spring-integration-cassandra.xsd -https\://www.springframework.org/schema/integration/cassandra/spring-integration-cassandra.xsd=org/springframework/integration/config/xml/spring-integration-cassandra.xsd - diff --git a/spring-integration-cassandra/src/main/resources/META-INF/spring.tooling b/spring-integration-cassandra/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index 7bb885685c5..00000000000 --- a/spring-integration-cassandra/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration cassandra namespace -http\://www.springframework.org/schema/integration/cassandra@name=integration cassandra Namespace -http\://www.springframework.org/schema/integration/cassandra@prefix=int-cassandra -http\://www.springframework.org/schema/integration/cassandra@icon=org/springframework/integration/config/xml/spring-integration-cassandra.gif diff --git a/spring-integration-cassandra/src/main/resources/org/springframework/integration/config/xml/spring-integration-cassandra.gif b/spring-integration-cassandra/src/main/resources/org/springframework/integration/config/xml/spring-integration-cassandra.gif deleted file mode 100644 index 750667e608f..00000000000 Binary files a/spring-integration-cassandra/src/main/resources/org/springframework/integration/config/xml/spring-integration-cassandra.gif and /dev/null differ diff --git a/spring-integration-cassandra/src/main/resources/org/springframework/integration/config/xml/spring-integration-cassandra.xsd b/spring-integration-cassandra/src/main/resources/org/springframework/integration/config/xml/spring-integration-cassandra.xsd deleted file mode 100644 index bcd56c2059b..00000000000 --- a/spring-integration-cassandra/src/main/resources/org/springframework/integration/config/xml/spring-integration-cassandra.xsd +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - - - - - - - - - Defines cassandra outbound channel adapter that - writes the contents of the - Message into Cassandra cluster - - - - - - - - - - Specify an expression for parameter variable placeholder in cql statement. - - - - - - - - - - - - - - Defines cassandra outbound gateway that - writes the contents of the - Message into Cassandra cluster - - - - - - - - - - Specify an expression for parameter variable placeholder in cql statement. - - - - - - - - Message Channel to which replies should be sent after being received from Cassandra - cluster. - - - - - - - - - - - - Unique ID for this gateway. - - - - - - - Message Channel to which Messages should be sent to Cassandra. - - - - - - - - - - - - - - - - - - Common configuration for cassandra adapters. - - - - - - - - - - Reference to an instance of - 'org.springframework.data.cassandra.core.ReactiveCassandraOperations'. - - - - - - - - - - - - Reference to an instance of - 'org.springframework.data.cassandra.core.cql.WriteOptions' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Statement to use in prepared statement - - - - - - - Statement expression that represent an executable query - - - - - - - ' async, reactive manner on the downstream - 'FluxMessageChannel' subscription or via 'Mono.subscribe()' in the handler, if one-way. - Otherwise the 'Mono.block()' is called immediately before returning from the handler. - ]]> - - - - - - - - - - Specifies the order for invocation when this - endpoint is connected as a - subscriber to a channel. - - - - - - - - - Expression to be evaluated against the Message to replace a query Parameter - - - - - - Name of the placeholder to be replaced - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/CassandraContainerTest.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/CassandraContainerTest.java deleted file mode 100644 index 1a95faa5ae3..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/CassandraContainerTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra; - -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.cassandra.CassandraContainer; -import org.testcontainers.junit.jupiter.Testcontainers; - -/** - * The base contract for JUnit tests based on the container for Apache Cassandra. - * The Testcontainers 'reuse' option must be disabled,so, Ryuk container is started - * and will clean all the containers up from this test suite after JVM exit. - * Since the MqSQL container instance is shared via static property, it is going to be - * started only once per JVM, therefore the target Docker container is reused automatically. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Testcontainers(disabledWithoutDocker = true) -public interface CassandraContainerTest { - - CassandraContainer CASSANDRA_CONTAINER = new CassandraContainer("cassandra"); - - @BeforeAll - static void startContainer() { - System.setProperty("datastax-java-driver.basic.request.timeout", "10 seconds"); - CASSANDRA_CONTAINER.start(); - } - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/IntegrationTestConfig.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/IntegrationTestConfig.java deleted file mode 100644 index 1fea947a29b..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/IntegrationTestConfig.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import org.springframework.context.annotation.Configuration; -import org.springframework.data.cassandra.config.AbstractReactiveCassandraConfiguration; -import org.springframework.data.cassandra.config.SchemaAction; -import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification; -import org.springframework.integration.cassandra.test.domain.Book; - -/** - * Setup any spring configuration for unit tests. - * Must be used in combination with {@link CassandraContainerTest}. - * - * @author David Webb - * @author Matthew T. Adams - * @author Artem Bilan - * - * @since 6.0 - */ -@Configuration -public class IntegrationTestConfig extends AbstractReactiveCassandraConfiguration { - - public String keyspaceName = randomKeyspaceName(); - - public static String randomKeyspaceName() { - return "ks" + UUID.randomUUID().toString().replace("-", ""); - } - - @Override - protected String getContactPoints() { - return CassandraContainerTest.CASSANDRA_CONTAINER.getContactPoint().getAddress().getHostAddress(); - } - - @Override - protected int getPort() { - return CassandraContainerTest.CASSANDRA_CONTAINER.getContactPoint().getPort(); - } - - @Override - public SchemaAction getSchemaAction() { - return SchemaAction.RECREATE; - } - - @Override - protected String getKeyspaceName() { - return this.keyspaceName; - } - - @Override - protected List getKeyspaceCreations() { - return Collections.singletonList( - CreateKeyspaceSpecification.createKeyspace(getKeyspaceName()) - .withSimpleReplication()); - } - - @Override - protected String getLocalDataCenter() { - return CassandraContainerTest.CASSANDRA_CONTAINER.getLocalDatacenter(); - } - - @Override - public String[] getEntityBasePackages() { - return new String[] {Book.class.getPackage().getName()}; - } - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests-context.xml b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests-context.xml deleted file mode 100644 index cf66eb13769..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests-context.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests.java deleted file mode 100644 index 3a7abb15ead..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.config; - -import java.util.ArrayList; -import java.util.List; - -import com.datastax.oss.driver.api.querybuilder.QueryBuilder; -import com.datastax.oss.driver.api.querybuilder.select.Select; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportResource; -import org.springframework.data.cassandra.core.ReactiveCassandraTemplate; -import org.springframework.data.cassandra.core.WriteResult; -import org.springframework.integration.cassandra.CassandraContainerTest; -import org.springframework.integration.cassandra.IntegrationTestConfig; -import org.springframework.integration.cassandra.test.domain.Book; -import org.springframework.integration.cassandra.test.domain.BookSampler; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Filippo Balicchia - * @author Artem Bilan - * - * @since 6.0 - */ -@SpringJUnitConfig(CassandraOutboundAdapterIntegrationTests.Config.class) -@DirtiesContext -class CassandraOutboundAdapterIntegrationTests implements CassandraContainerTest { - - @Autowired - private ReactiveCassandraTemplate cassandraTemplate; - - @Autowired - private DirectChannel cassandraMessageHandler1; - - @Autowired - private DirectChannel cassandraMessageHandler2; - - @Autowired - private DirectChannel cassandraMessageHandler3; - - @Autowired - private DirectChannel cassandraMessageHandler4; - - @Autowired - private DirectChannel inputChannel; - - @Autowired - private FluxMessageChannel resultChannel; - - @Test - void testBasicCassandraInsert() { - Book b1 = BookSampler.getBook(); - Message message = MessageBuilder.withPayload(b1).build(); - this.cassandraMessageHandler1.send(message); - Select select = QueryBuilder.selectFrom("book").all(); - List books = this.cassandraTemplate.select(select.build(), Book.class).collectList().block(); - assertThat(books).hasSize(1); - this.cassandraTemplate.delete(b1); - } - - @Test - void testCassandraBatchInsertAndSelectStatement() { - List books = BookSampler.getBookList(5); - this.cassandraMessageHandler2.send(new GenericMessage<>(books)); - Message message = MessageBuilder.withPayload("Cassandra Puppy Guru").setHeader("limit", 2).build(); - this.inputChannel.send(message); - - Mono testMono = - Mono.from(this.resultChannel) - .map(Message::getPayload) - .cast(WriteResult.class) - .map(r -> r.getRows().size()); - - StepVerifier.create(testMono) - .expectNext(2) - .expectComplete() - .verify(); - - this.cassandraMessageHandler1.send(new GenericMessage<>(QueryBuilder.truncate("book").build())); - - } - - @Test - void testCassandraBatchIngest() { - List books = BookSampler.getBookList(5); - List> ingestBooks = new ArrayList<>(); - for (Book b : books) { - - List l = new ArrayList<>(); - l.add(b.isbn()); - l.add(b.title()); - l.add(b.author()); - l.add(b.pages()); - l.add(b.saleDate()); - l.add(b.isInStock()); - ingestBooks.add(l); - } - - Message>> message = MessageBuilder.withPayload(ingestBooks).build(); - this.cassandraMessageHandler3.send(message); - Select select = QueryBuilder.selectFrom("book").all(); - books = this.cassandraTemplate.select(select.build(), Book.class).collectList().block(); - assertThat(books).hasSize(5); - this.cassandraTemplate.batchOps().delete(books); - } - - @Test - void testExpressionTruncate() { - Message message = MessageBuilder.withPayload(BookSampler.getBook()).build(); - this.cassandraMessageHandler1.send(message); - Select select = QueryBuilder.selectFrom("book").all(); - List books = this.cassandraTemplate.select(select.build(), Book.class).collectList().block(); - assertThat(books).hasSize(1); - this.cassandraMessageHandler4.send(MessageBuilder.withPayload("Empty").build()); - books = this.cassandraTemplate.select(select.build(), Book.class).collectList().block(); - assertThat(books).hasSize(0); - } - - @Configuration - @EnableIntegration - @ImportResource("org/springframework/integration/cassandra/config/CassandraOutboundAdapterIntegrationTests-context.xml") - public static class Config extends IntegrationTestConfig { - - } - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterParserTests-context.xml b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterParserTests-context.xml deleted file mode 100644 index 80dbbe55585..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterParserTests-context.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterParserTests.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterParserTests.java deleted file mode 100644 index 429cbe61fc3..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/config/CassandraOutboundAdapterParserTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.integration.cassandra.outbound.CassandraMessageHandler; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Filippo Balicchia - * @author Artem Bilan - * - * @since 6.0 - */ -@SpringJUnitConfig -@DirtiesContext -class CassandraOutboundAdapterParserTests { - - @Autowired - private ApplicationContext context; - - @Test - void minimalConfig() { - CassandraMessageHandler handler = - TestUtils.getPropertyValue(this.context.getBean("outbound1.adapter"), "handler", - CassandraMessageHandler.class); - - assertThat(TestUtils.getPropertyValue(handler, "componentName")).isEqualTo("outbound1.adapter"); - assertThat(TestUtils.getPropertyValue(handler, "mode")).isEqualTo(CassandraMessageHandler.Type.INSERT); - assertThat(TestUtils.getPropertyValue(handler, "cassandraOperations")) - .isSameAs(this.context.getBean("cassandraTemplate")); - assertThat(TestUtils.getPropertyValue(handler, "writeOptions")).isSameAs(this.context.getBean("writeOptions")); - assertThat(TestUtils.getPropertyValue(handler, "async", Boolean.class)).isFalse(); - } - - @Test - void ingestConfig() { - CassandraMessageHandler handler = - TestUtils.getPropertyValue(this.context.getBean("outbound2"), "handler", - CassandraMessageHandler.class); - - assertThat(TestUtils.getPropertyValue(handler, "ingestQuery")) - .isEqualTo("insert into book (isbn, title, author, pages, saleDate, isInStock) " + - "values (?, ?, ?, ?, ?, ?)"); - assertThat(TestUtils.getPropertyValue(handler, "producesReply", Boolean.class)).isFalse(); - } - - @Test - void fullConfig() { - CassandraMessageHandler handler = - TestUtils.getPropertyValue(this.context.getBean("outgateway"), "handler", - CassandraMessageHandler.class); - - assertThat(TestUtils.getPropertyValue(handler, "producesReply", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(handler, "mode")).isEqualTo(CassandraMessageHandler.Type.STATEMENT); - assertThat(TestUtils.getPropertyValue(handler, "writeOptions")).isSameAs(this.context.getBean("writeOptions")); - } - - @Test - void statementConfig() { - CassandraMessageHandler handler = - TestUtils.getPropertyValue(this.context.getBean("outbound4.adapter"), "handler", - CassandraMessageHandler.class); - - assertThat(TestUtils.getPropertyValue(handler, "componentName")).isEqualTo("outbound4.adapter"); - assertThat(TestUtils.getPropertyValue(handler, "mode")).isEqualTo(CassandraMessageHandler.Type.STATEMENT); - assertThat(TestUtils.getPropertyValue(handler, "cassandraOperations")) - .isSameAs(this.context.getBean("cassandraTemplate")); - assertThat(TestUtils.getPropertyValue(handler, "writeOptions")).isSameAs(this.context.getBean("writeOptions")); - } - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/dsl/CassandraDslTests.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/dsl/CassandraDslTests.java deleted file mode 100644 index fe1e74013bb..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/dsl/CassandraDslTests.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.dsl; - -import java.time.Duration; - -import com.datastax.oss.driver.api.core.ConsistencyLevel; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.cassandra.core.InsertOptions; -import org.springframework.data.cassandra.core.ReactiveCassandraOperations; -import org.springframework.data.cassandra.core.WriteResult; -import org.springframework.integration.cassandra.CassandraContainerTest; -import org.springframework.integration.cassandra.IntegrationTestConfig; -import org.springframework.integration.cassandra.test.domain.BookSampler; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -/** - * @author Artem Bilan - * - * @since 6.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class CassandraDslTests implements CassandraContainerTest { - - @Autowired - @Qualifier("cassandraTruncateFlow.input") - MessageChannel cassandraTruncateFlowInput; - - @Autowired - @Qualifier("cassandraInsertFlow.input") - MessageChannel cassandraInsertFlowInput; - - @Autowired - @Qualifier("cassandraSelectFlow.input") - MessageChannel cassandraSelectFlowInput; - - @Autowired - FluxMessageChannel resultChannel; - - @Test - void testCassandraDslConfiguration() { - this.cassandraInsertFlowInput.send(new GenericMessage<>(BookSampler.getBookList(5))); - - Mono testMono = - Mono.from(this.resultChannel) - .map(Message::getPayload) - .cast(WriteResult.class) - .map(r -> r.getRows().size()); - - StepVerifier stepVerifier = StepVerifier.create(testMono) - .expectNext(1) - .expectComplete() - .verifyLater(); - - this.cassandraSelectFlowInput.send(MessageBuilder.withPayload("Cassandra Guru").setHeader("limit", 2).build()); - - stepVerifier.verify(Duration.ofSeconds(10)); - - this.cassandraTruncateFlowInput.send(new GenericMessage<>("")); - } - - @Configuration - @EnableIntegration - public static class Config extends IntegrationTestConfig { - - @Bean - IntegrationFlow cassandraTruncateFlow(ReactiveCassandraOperations cassandraOperations) { - return flow -> flow - .handle(Cassandra.outboundChannelAdapter(cassandraOperations) - .statementExpression("T(QueryBuilder).truncate('book').build()"), - e -> e.async(false)); - } - - @Bean - IntegrationFlow cassandraInsertFlow(ReactiveCassandraOperations cassandraOperations) { - return flow -> flow - .handle(Cassandra.outboundChannelAdapter(cassandraOperations) - .writeOptions(InsertOptions.builder() - .ttl(60) - .consistencyLevel(ConsistencyLevel.ONE) - .build()), - e -> e.async(false)); - } - - @Bean - IntegrationFlow cassandraSelectFlow(ReactiveCassandraOperations cassandraOperations) { - return flow -> flow - .handle(Cassandra.outboundGateway(cassandraOperations) - .query("SELECT * FROM book WHERE author = :author limit :size") - .parameter("author", "payload") - .parameter("size", m -> m.getHeaders().get("limit"))) - .channel(c -> c.flux("resultChannel")); - } - - } - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandlerTests.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandlerTests.java deleted file mode 100644 index 27a84d06afe..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/outbound/CassandraMessageHandlerTests.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.outbound; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.datastax.oss.driver.api.core.ConsistencyLevel; -import com.datastax.oss.driver.api.querybuilder.QueryBuilder; -import com.datastax.oss.driver.api.querybuilder.select.Select; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.cassandra.core.CassandraOperations; -import org.springframework.data.cassandra.core.InsertOptions; -import org.springframework.data.cassandra.core.ReactiveCassandraOperations; -import org.springframework.data.cassandra.core.WriteResult; -import org.springframework.data.cassandra.core.cql.WriteOptions; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.cassandra.CassandraContainerTest; -import org.springframework.integration.cassandra.IntegrationTestConfig; -import org.springframework.integration.cassandra.test.domain.Book; -import org.springframework.integration.cassandra.test.domain.BookSampler; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Soby Chacko - * @author Artem Bilan - * - * @since 6.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class CassandraMessageHandlerTests implements CassandraContainerTest { - - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - - @Autowired - public MessageHandler cassandraMessageHandler1; - - @Autowired - public MessageHandler cassandraMessageHandler2; - - @Autowired - public MessageHandler cassandraMessageHandler3; - - @Autowired - public MessageHandler cassandraMessageHandler4; - - @Autowired - public CassandraOperations template; - - @Autowired - public FluxMessageChannel resultChannel; - - @Test - void testBasicCassandraInsert() { - Book b1 = BookSampler.getBook(); - - Message message = MessageBuilder.withPayload(b1).build(); - this.cassandraMessageHandler1.handleMessage(message); - - Select select = QueryBuilder.selectFrom("book").all(); - List books = this.template.select(select.build(), Book.class); - assertThat(books).hasSize(1); - - this.template.delete(b1); - } - - @Test - void testCassandraBatchInsertAndSelectStatement() { - List books = BookSampler.getBookList(5); - - this.cassandraMessageHandler2.handleMessage(new GenericMessage<>(books)); - - Message message = MessageBuilder.withPayload("Cassandra Guru").setHeader("limit", 2).build(); - this.cassandraMessageHandler4.handleMessage(message); - - Mono testMono = - Mono.from(this.resultChannel) - .map(Message::getPayload) - .cast(WriteResult.class) - .map(r -> r.getRows().size()); - - StepVerifier.create(testMono) - .expectNext(1) - .expectComplete() - .verify(); - - this.cassandraMessageHandler1.handleMessage(new GenericMessage<>(QueryBuilder.truncate("book").build())); - } - - @Test - void testCassandraBatchIngest() { - List books = BookSampler.getBookList(5); - List> ingestBooks = - books.stream() - .map(book -> - List.of( - book.isbn(), - book.title(), - book.author(), - book.pages(), - book.saleDate(), - book.isInStock())) - .toList(); - - this.cassandraMessageHandler3.handleMessage(MessageBuilder.withPayload(ingestBooks).build()); - - Select select = QueryBuilder.selectFrom("book").all(); - books = this.template.select(select.build(), Book.class); - assertThat(books).hasSize(5); - - this.template.batchOps().delete(books); - } - - @Configuration - @EnableIntegration - public static class Config extends IntegrationTestConfig { - - @Autowired - public ReactiveCassandraOperations template; - - @Bean - public MessageHandler cassandraMessageHandler1() { - CassandraMessageHandler cassandraMessageHandler = new CassandraMessageHandler(this.template); - cassandraMessageHandler.setAsync(false); - return cassandraMessageHandler; - } - - @Bean - public PollableChannel messageChannel() { - return new NullChannel(); - } - - @Bean - public MessageHandler cassandraMessageHandler2() { - CassandraMessageHandler cassandraMessageHandler = new CassandraMessageHandler(this.template); - - WriteOptions options = - InsertOptions.builder() - .ttl(60) - .consistencyLevel(ConsistencyLevel.ONE) - .build(); - - cassandraMessageHandler.setWriteOptions(options); - cassandraMessageHandler.setOutputChannel(messageChannel()); - cassandraMessageHandler.setAsync(false); - return cassandraMessageHandler; - } - - @Bean - public MessageHandler cassandraMessageHandler3() { - CassandraMessageHandler cassandraMessageHandler = new CassandraMessageHandler(this.template); - String cqlIngest = - "insert into book (isbn, title, author, pages, saleDate, isInStock) values (?, ?, ?, ?, ?, ?)"; - cassandraMessageHandler.setIngestQuery(cqlIngest); - cassandraMessageHandler.setAsync(false); - return cassandraMessageHandler; - } - - @Bean - public FluxMessageChannel resultChannel() { - return new FluxMessageChannel(); - } - - @Bean - public MessageHandler cassandraMessageHandler4() { - CassandraMessageHandler cassandraMessageHandler = new CassandraMessageHandler(this.template); - cassandraMessageHandler.setQuery("SELECT * FROM book WHERE author = :author limit :size"); - - Map params = new HashMap<>(); - params.put("author", PARSER.parseExpression("payload")); - params.put("size", PARSER.parseExpression("headers.limit")); - - cassandraMessageHandler.setParameterExpressions(params); - - cassandraMessageHandler.setOutputChannel(resultChannel()); - cassandraMessageHandler.setProducesReply(true); - return cassandraMessageHandler; - } - - } - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/test/domain/Book.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/test/domain/Book.java deleted file mode 100644 index 7f991805e54..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/test/domain/Book.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.test.domain; - -import java.time.LocalDate; - -import org.springframework.data.cassandra.core.mapping.Indexed; -import org.springframework.data.cassandra.core.mapping.PrimaryKey; -import org.springframework.data.cassandra.core.mapping.Table; - -/** - * Test POJO - * - * @author David Webb - * @author Artem Bilan - * - * @since 6.0 - */ -@Table("book") -public record Book( - @PrimaryKey String isbn, - String title, - @Indexed String author, - Integer pages, - LocalDate saleDate, - Boolean isInStock) { - -} diff --git a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/test/domain/BookSampler.java b/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/test/domain/BookSampler.java deleted file mode 100644 index 83020f5583c..00000000000 --- a/spring-integration-cassandra/src/test/java/org/springframework/integration/cassandra/test/domain/BookSampler.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.cassandra.test.domain; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * @author Filippo Balicchia - * @author Artem Bilan - * - * @since 6.0 - */ -public final class BookSampler { - - public static List getBookList(int numBooks) { - List books = new ArrayList<>(); - for (int i = 0; i < numBooks - 1; i++) { - books.add(new Book(UUID.randomUUID().toString(), "Spring Data Cassandra Guide", "Cassandra Guru puppy", - i * 10 + 5, LocalDate.now(), true)); - } - books.add(getBook()); - return books; - } - - public static Book getBook() { - return new Book("123456-1", "Spring Integration Cassandra", "Cassandra Guru", 521, LocalDate.now(), true); - } - - private BookSampler() { - } - -} diff --git a/spring-integration-cassandra/src/test/resources/log4j2-test.xml b/spring-integration-cassandra/src/test/resources/log4j2-test.xml deleted file mode 100644 index c916f70cee0..00000000000 --- a/spring-integration-cassandra/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java deleted file mode 100644 index cdd00976b50..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationMessageHeaderAccessor.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import java.io.Closeable; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiFunction; - -import org.jspecify.annotations.Nullable; -import reactor.util.context.ContextView; - -import org.springframework.integration.acks.AcknowledgmentCallback; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * - * Adds standard SI Headers. - * - * @author Andy Wilkinson - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.0 - * - */ -public class IntegrationMessageHeaderAccessor extends MessageHeaderAccessor { - - public static final String CORRELATION_ID = "correlationId"; - - public static final String EXPIRATION_DATE = "expirationDate"; - - public static final String PRIORITY = "priority"; - - public static final String SEQUENCE_NUMBER = "sequenceNumber"; - - public static final String SEQUENCE_SIZE = "sequenceSize"; - - public static final String SEQUENCE_DETAILS = "sequenceDetails"; - - public static final String ROUTING_SLIP = "routingSlip"; - - public static final String DUPLICATE_MESSAGE = "duplicateMessage"; - - public static final String CLOSEABLE_RESOURCE = "closeableResource"; - - public static final String DELIVERY_ATTEMPT = "deliveryAttempt"; - - /** - * A callback to acknowledge message delivery. The type of the header value depends on - * the context in which the header is used. See the reference manual for more - * information. - */ - public static final String ACKNOWLEDGMENT_CALLBACK = "acknowledgmentCallback"; - - /** - * Raw source message. - */ - public static final String SOURCE_DATA = "sourceData"; - - /** - * The header for {@link reactor.util.context.ContextView}. - */ - public static final String REACTOR_CONTEXT = "reactorContext"; - - /** - * The header for Control Bus command arguments. Must be a list of values. - */ - public static final String CONTROL_BUS_ARGUMENTS = "controlBusArguments"; - - private static final BiFunction TYPE_VERIFY_MESSAGE_FUNCTION = - (name, trailer) -> "The '" + name + trailer; - - private Set readOnlyHeaders = new HashSet<>(); - - public IntegrationMessageHeaderAccessor(@Nullable Message message) { - super(message); - } - - /** - * Specify a list of headers which should be considered as read only and prohibited - * from being populated in the message. - * @param readOnlyHeaders the list of headers for {@code readOnly} mode. Defaults to - * {@link org.springframework.messaging.MessageHeaders#ID} and - * {@link org.springframework.messaging.MessageHeaders#TIMESTAMP}. - * @since 4.3.2 - * @see #isReadOnly(String) - */ - public void setReadOnlyHeaders(String... readOnlyHeaders) { - Assert.noNullElements(readOnlyHeaders, "'readOnlyHeaders' must not contain null items."); - if (!ObjectUtils.isEmpty(readOnlyHeaders)) { - this.readOnlyHeaders = new HashSet<>(Arrays.asList(readOnlyHeaders)); - } - } - - @Nullable - public Long getExpirationDate() { - return getHeader(EXPIRATION_DATE, Long.class); - } - - @Nullable - public Object getCorrelationId() { - return getHeader(CORRELATION_ID); - } - - public int getSequenceNumber() { - Number sequenceNumber = getHeader(SEQUENCE_NUMBER, Number.class); - return (sequenceNumber != null ? sequenceNumber.intValue() : 0); - } - - public int getSequenceSize() { - Number sequenceSize = getHeader(SEQUENCE_SIZE, Number.class); - return (sequenceSize != null ? sequenceSize.intValue() : 0); - } - - @Nullable - public Integer getPriority() { - Number priority = getHeader(PRIORITY, Number.class); - return (priority != null ? priority.intValue() : null); - } - - /** - * If the payload was created by a {@link Closeable} that needs to remain - * open until the payload is consumed, the resource will be added to this - * header. After the payload is consumed the {@link Closeable} should be - * closed. Usually this must occur in an endpoint close to the message - * origin in the flow, and in the same JVM. - * @return the {@link Closeable}. - * @since 4.3 - */ - @Nullable - public Closeable getCloseableResource() { - return getHeader(CLOSEABLE_RESOURCE, Closeable.class); - } - - /** - * Return the acknowledgment callback, if present. - * @return the callback. - * @since 5.0.1 - */ - @Nullable - public AcknowledgmentCallback getAcknowledgmentCallback() { - return getHeader(ACKNOWLEDGMENT_CALLBACK, AcknowledgmentCallback.class); - } - - /** - * When a message-driven endpoint supports retry implicitly, this - * header is incremented for each delivery attempt. - * @return the delivery attempt. - * @since 5.0.1 - */ - @Nullable - public AtomicInteger getDeliveryAttempt() { - return getHeader(DELIVERY_ATTEMPT, AtomicInteger.class); - } - - /** - * Get the source data header, if present. - * @param the data type. - * @return the source header. - * @since 5.1.6 - */ - @SuppressWarnings("unchecked") - @Nullable - public T getSourceData() { - return (T) getHeader(SOURCE_DATA); - } - - /** - * Get a {@link ContextView} header if present. - * @return the {@link ContextView} header if present. - * @since 6.0.5 - */ - @Nullable - public ContextView getReactorContext() { - return getHeader(REACTOR_CONTEXT, ContextView.class); - } - - @SuppressWarnings("unchecked") - @Nullable - public T getHeader(String key, Class type) { - Object value = getHeader(key); - if (value == null) { - return null; - } - if (!type.isAssignableFrom(value.getClass())) { - throw new IllegalArgumentException("Incorrect type specified for header '" + key + "'. Expected [" + type - + "] but actual type is [" + value.getClass() + "]"); - } - return (T) value; - } - - @Override - protected void verifyType(String headerName, Object headerValue) { - if (headerName != null && headerValue != null) { - super.verifyType(headerName, headerValue); - if (IntegrationMessageHeaderAccessor.EXPIRATION_DATE.equals(headerName)) { - Assert.isTrue(headerValue instanceof Date || headerValue instanceof Long, - TYPE_VERIFY_MESSAGE_FUNCTION.apply(headerName, "' header value must be a Date or Long.")); - } - else if (IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER.equals(headerName) - || IntegrationMessageHeaderAccessor.SEQUENCE_SIZE.equals(headerName) - || IntegrationMessageHeaderAccessor.PRIORITY.equals(headerName)) { - Assert.isTrue(Number.class.isAssignableFrom(headerValue.getClass()), - TYPE_VERIFY_MESSAGE_FUNCTION.apply(headerName, "' header value must be a Number.")); - } - else if (IntegrationMessageHeaderAccessor.ROUTING_SLIP.equals(headerName)) { - Assert.isTrue(Map.class.isAssignableFrom(headerValue.getClass()), - TYPE_VERIFY_MESSAGE_FUNCTION.apply(headerName, "' header value must be a Map.")); - } - else if (IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE.equals(headerName)) { - Assert.isTrue(Boolean.class.isAssignableFrom(headerValue.getClass()), - TYPE_VERIFY_MESSAGE_FUNCTION.apply(headerName, "' header value must be an Boolean.")); - } - } - } - - @Override - public boolean isReadOnly(String headerName) { - return super.isReadOnly(headerName) || this.readOnlyHeaders.contains(headerName); - } - - @Override - public Map toMap() { - if (ObjectUtils.isEmpty(this.readOnlyHeaders)) { - return super.toMap(); - } - else { - Map headers = super.toMap(); - for (String header : this.readOnlyHeaders) { - headers.remove(header); - } - return headers; - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationPattern.java b/spring-integration-core/src/main/java/org/springframework/integration/IntegrationPattern.java deleted file mode 100644 index 78da3048a88..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationPattern.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -/** - * Indicates that a component implements some Enterprise Integration Pattern. - * - * @author Artem Bilan - * - * @since 5.3 - * - * @see IntegrationPatternType - * @see EIP official site - */ -public interface IntegrationPattern { - - /** - * Return a pattern type this component implements. - * @return the {@link IntegrationPatternType} this component implements. - */ - IntegrationPatternType getIntegrationPatternType(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationPatternType.java b/spring-integration-core/src/main/java/org/springframework/integration/IntegrationPatternType.java deleted file mode 100644 index 282ea66e014..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/IntegrationPatternType.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * The Enterprise Integration Pattern types. - * Used to indicate which pattern a target component implements. - * - * @author Artem Bilan - * - * @since 5.3 - */ -public enum IntegrationPatternType { // NOSONAR Initialization circularity is useful for static view - - message_channel(IntegrationPatternCategory.messaging_channel), - - publish_subscribe_channel(IntegrationPatternCategory.messaging_channel), - - executor_channel(IntegrationPatternCategory.messaging_channel), - - pollable_channel(IntegrationPatternCategory.messaging_channel), - - reactive_channel(IntegrationPatternCategory.messaging_channel), - - null_channel(IntegrationPatternCategory.messaging_channel), - - bridge(IntegrationPatternCategory.messaging_endpoint), - - service_activator(IntegrationPatternCategory.messaging_endpoint), - - outbound_channel_adapter(IntegrationPatternCategory.messaging_endpoint), - - inbound_channel_adapter(IntegrationPatternCategory.messaging_endpoint), - - outbound_gateway(IntegrationPatternCategory.messaging_endpoint), - - inbound_gateway(IntegrationPatternCategory.messaging_endpoint), - - splitter(IntegrationPatternCategory.message_routing), - - transformer(IntegrationPatternCategory.message_transformation), - - header_enricher(IntegrationPatternCategory.message_transformation), - - filter(IntegrationPatternCategory.message_routing), - - content_enricher(IntegrationPatternCategory.message_transformation), - - header_filter(IntegrationPatternCategory.message_transformation), - - claim_check_in(IntegrationPatternCategory.message_transformation), - - claim_check_out(IntegrationPatternCategory.message_transformation), - - aggregator(IntegrationPatternCategory.message_routing), - - resequencer(IntegrationPatternCategory.message_routing), - - barrier(IntegrationPatternCategory.message_routing), - - chain(IntegrationPatternCategory.message_routing), - - scatter_gather(IntegrationPatternCategory.message_routing), - - delayer(IntegrationPatternCategory.message_routing), - - control_bus(IntegrationPatternCategory.system_management), - - router(IntegrationPatternCategory.message_routing), - - recipient_list_router(IntegrationPatternCategory.message_routing); - - private final IntegrationPatternCategory patternCategory; - - IntegrationPatternType(IntegrationPatternCategory patternCategory) { - this.patternCategory = patternCategory; - } - - public IntegrationPatternCategory getPatternCategory() { - return this.patternCategory; - } - - /** - * The Enterprise Integration Pattern categories. - * Used to indicate which pattern category a target component belongs. - */ - public enum IntegrationPatternCategory { - - messaging_channel( - message_channel, - publish_subscribe_channel, - executor_channel, - pollable_channel, - reactive_channel, - null_channel), - - messaging_endpoint( - service_activator, - outbound_channel_adapter, - inbound_channel_adapter, - outbound_gateway, - inbound_gateway, - bridge), - - message_routing( - splitter, - filter, - aggregator, - resequencer, - barrier, - chain, - scatter_gather, - delayer, - router, - recipient_list_router), - - message_transformation( - transformer, - header_enricher, - content_enricher, - header_filter, - claim_check_in, - claim_check_out), - - system_management(control_bus); - - private final IntegrationPatternType[] patternTypes; - - IntegrationPatternCategory(IntegrationPatternType... patternTypes) { - this.patternTypes = Arrays.copyOf(patternTypes, patternTypes.length); - } - - public Set getPatternTypes() { - return Arrays.stream(this.patternTypes).collect(Collectors.toSet()); - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/JavaUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/JavaUtils.java deleted file mode 100644 index bfd2f80f528..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/JavaUtils.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import org.jspecify.annotations.Nullable; - -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -/** - * Chained utility methods to simplify some Java repetitive code. Obtain a reference to - * the singleton {@link #INSTANCE} and then chain calls to the utility methods. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.1.3 - */ -public final class JavaUtils { - - /** - * The singleton instance of this utility class. - */ - public static final JavaUtils INSTANCE = new JavaUtils(); - - private JavaUtils() { - } - - /** - * Invoke {@link Consumer#accept(Object)} with the value if the condition is true. - * @param condition the condition. - * @param value the value. - * @param consumer the consumer. - * @param the value type. - * @return this. - */ - public JavaUtils acceptIfCondition(boolean condition, @Nullable T value, Consumer consumer) { - if (condition) { - consumer.accept(value); - } - return this; - } - - /** - * Invoke {@link Consumer#accept(Object)} with the value if it is not null. - * @param value the value. - * @param consumer the consumer. - * @param the value type. - * @return this. - */ - public JavaUtils acceptIfNotNull(@Nullable T value, Consumer consumer) { - if (value != null) { - consumer.accept(value); - } - return this; - } - - /** - * Invoke {@link Consumer#accept(Object)} with the value if it is not null or empty. - * @param value the value. - * @param consumer the consumer. - * @return this. - * @since 5.2 - */ - public JavaUtils acceptIfHasText(@Nullable String value, Consumer consumer) { - if (StringUtils.hasText(value)) { - consumer.accept(value); - } - return this; - } - - /** - * Invoke {@link Consumer#accept(Object)} with the value if it is not null or empty. - * @param value the value. - * @param consumer the consumer. - * @param the value type. - * @return this. - * @since 5.2 - */ - public JavaUtils acceptIfNotEmpty(@Nullable List value, Consumer> consumer) { - if (!CollectionUtils.isEmpty(value)) { - consumer.accept(value); - } - return this; - } - - /** - * Invoke {@link Consumer#accept(Object)} with the value if it is not null or empty. - * @param value the value. - * @param consumer the consumer. - * @param the value type. - * @return this. - * @since 5.2 - */ - public JavaUtils acceptIfNotEmpty(@Nullable T @Nullable [] value, Consumer consumer) { - if (!ObjectUtils.isEmpty(value)) { - consumer.accept(value); - } - return this; - } - - /** - * Invoke {@link BiConsumer#accept(Object, Object)} with the arguments if the - * condition is true. - * @param condition the condition. - * @param t1 the first consumer argument - * @param t2 the second consumer argument - * @param consumer the consumer. - * @param the first argument type. - * @param the second argument type. - * @return this. - * @since 5.2 - */ - public JavaUtils acceptIfCondition(boolean condition, @Nullable T1 t1, @Nullable T2 t2, - BiConsumer consumer) { - - if (condition) { - consumer.accept(t1, t2); - } - return this; - } - - /** - * Invoke {@link BiConsumer#accept(Object, Object)} with the arguments if the t2 - * argument is not null. - * @param t1 the first argument - * @param t2 the second consumer argument - * @param consumer the consumer. - * @param the first argument type. - * @param the second argument type. - * @return this. - * @since 5.2 - */ - public JavaUtils acceptIfNotNull(T1 t1, @Nullable T2 t2, BiConsumer consumer) { - if (t2 != null) { - consumer.accept(t1, t2); - } - return this; - } - - /** - * Invoke {@link BiConsumer#accept(Object, Object)} with the arguments if the value - * argument is not null or empty. - * @param t1 the first consumer argument. - * @param value the second consumer argument - * @param the first argument type. - * @param consumer the consumer. - * @return this. - * @since 5.2 - */ - public JavaUtils acceptIfHasText(T t1, @Nullable String value, BiConsumer consumer) { - if (StringUtils.hasText(value)) { - consumer.accept(t1, value); - } - return this; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageDispatchingException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageDispatchingException.java deleted file mode 100644 index 1cba5009688..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/MessageDispatchingException.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; - -/** - * Exception that indicates an internal error occurred within a - * {@link org.springframework.integration.dispatcher.MessageDispatcher} - * preventing message delivery. - * - * @author Gary Russell - * @since 2.1 - * - */ -@SuppressWarnings("serial") -public class MessageDispatchingException extends MessageDeliveryException { - - public MessageDispatchingException(String description) { - super(description); - } - - public MessageDispatchingException(Message undeliveredMessage, - String description, Throwable cause) { - super(undeliveredMessage, description, cause); - } - - public MessageDispatchingException(Message undeliveredMessage, - String description) { - super(undeliveredMessage, description); - } - - public MessageDispatchingException(Message undeliveredMessage) { - super(undeliveredMessage); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageRejectedException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageRejectedException.java deleted file mode 100644 index 0062fc6b37a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/MessageRejectedException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandlingException; - -/** - * Exception that indicates a message has been rejected by a selector. - * - * @author Mark Fisher - * @author Artem Bilan - */ -@SuppressWarnings("serial") -public class MessageRejectedException extends MessageHandlingException { - - public MessageRejectedException(Message failedMessage, String description) { - super(failedMessage, description); - } - - public MessageRejectedException(Message failedMessage, String description, Throwable cause) { - super(failedMessage, description, cause); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageTimeoutException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageTimeoutException.java deleted file mode 100644 index 67346a3f773..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/MessageTimeoutException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; - -/** - * Exception that indicates a timeout elapsed prior to successful message delivery. - * - * @author Mark Fisher - */ -@SuppressWarnings("serial") -public class MessageTimeoutException extends MessageDeliveryException { - - public MessageTimeoutException(String description) { - super(description); - } - - public MessageTimeoutException(Message failedMessage, String description, Throwable cause) { - super(failedMessage, description, cause); - } - - public MessageTimeoutException(Message failedMessage, String description) { - super(failedMessage, description); - } - - public MessageTimeoutException(Message failedMessage) { - super(failedMessage); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/StaticMessageHeaderAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/StaticMessageHeaderAccessor.java deleted file mode 100644 index 967e7562a75..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/StaticMessageHeaderAccessor.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration; - -import java.io.Closeable; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -import org.jspecify.annotations.Nullable; -import reactor.util.context.Context; -import reactor.util.context.ContextView; - -import org.springframework.integration.acks.AcknowledgmentCallback; -import org.springframework.integration.acks.SimpleAcknowledgment; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.MimeType; - -/** - * Lightweight type-safe header accessor avoiding object - * creation just to access a header. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0.1 - * - * @see IntegrationMessageHeaderAccessor - */ -public final class StaticMessageHeaderAccessor { - - private StaticMessageHeaderAccessor() { - } - - @Nullable - public static UUID getId(Message message) { - Object value = message.getHeaders().get(MessageHeaders.ID); - if (value == null) { - return null; - } - return (value instanceof UUID ? (UUID) value : UUID.fromString(value.toString())); - } - - @Nullable - public static Long getTimestamp(Message message) { - Object value = message.getHeaders().get(MessageHeaders.TIMESTAMP); - if (value == null) { - return null; - } - return (value instanceof Long ? (Long) value : Long.parseLong(value.toString())); - } - - @Nullable - public static MimeType getContentType(Message message) { - Object value = message.getHeaders().get(MessageHeaders.CONTENT_TYPE); - if (value == null) { - return null; - } - return (value instanceof MimeType ? (MimeType) value : MimeType.valueOf(value.toString())); - } - - @Nullable - public static Long getExpirationDate(Message message) { - return message.getHeaders().get(IntegrationMessageHeaderAccessor.EXPIRATION_DATE, Long.class); - } - - public static int getSequenceNumber(Message message) { - Number sequenceNumber = message.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, - Number.class); - return (sequenceNumber != null ? sequenceNumber.intValue() : 0); - } - - public static int getSequenceSize(Message message) { - Number sequenceSize = message.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, Number.class); - return (sequenceSize != null ? sequenceSize.intValue() : 0); - } - - @Nullable - public static Integer getPriority(Message message) { - Number priority = message.getHeaders().get(IntegrationMessageHeaderAccessor.PRIORITY, Number.class); - return (priority != null ? priority.intValue() : null); - } - - @Nullable - public static Closeable getCloseableResource(Message message) { - return message.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, Closeable.class); - } - - @Nullable - public static AtomicInteger getDeliveryAttempt(Message message) { - return message.getHeaders().get(IntegrationMessageHeaderAccessor.DELIVERY_ATTEMPT, AtomicInteger.class); - } - - @Nullable - public static AcknowledgmentCallback getAcknowledgmentCallback(Message message) { - return message.getHeaders().get(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, - AcknowledgmentCallback.class); - } - - @Nullable - public static SimpleAcknowledgment getAcknowledgment(Message message) { - return message.getHeaders().get(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, - SimpleAcknowledgment.class); - } - - @SuppressWarnings("unchecked") - @Nullable - public static T getSourceData(Message message) { - return (T) message.getHeaders().get(IntegrationMessageHeaderAccessor.SOURCE_DATA); - } - - /** - * Get a {@link ContextView} header if present. - * @param message the message to get a header from. - * @return the {@link ContextView} header if present. - * @since 6.0.5 - */ - public static ContextView getReactorContext(Message message) { - ContextView reactorContext = message.getHeaders() - .get(IntegrationMessageHeaderAccessor.REACTOR_CONTEXT, ContextView.class); - if (reactorContext == null) { - reactorContext = Context.empty(); - } - return reactorContext; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/acks/AckUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/acks/AckUtils.java deleted file mode 100644 index 9e127aa22ef..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/acks/AckUtils.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.acks; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.acks.AcknowledgmentCallback.Status; - -/** - * Utility methods for acting on {@link AcknowledgmentCallback} headers. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0.1 - * - */ -public final class AckUtils { - - private AckUtils() { - } - - /** - * ACCEPT an {@link AcknowledgmentCallback} if it's not null, supports auto ack - * and is not already ack'd. - * @param ackCallback the callback. - */ - public static void autoAck(@Nullable AcknowledgmentCallback ackCallback) { - if (ackCallback != null && ackCallback.isAutoAck() && !ackCallback.isAcknowledged()) { - ackCallback.acknowledge(Status.ACCEPT); - } - } - - /** - * REJECT an {@link AcknowledgmentCallback} if it's not null, supports auto ack - * and is not already ack'd. - * @param ackCallback the callback. - */ - public static void autoNack(@Nullable AcknowledgmentCallback ackCallback) { - if (ackCallback != null && ackCallback.isAutoAck() && !ackCallback.isAcknowledged()) { - ackCallback.acknowledge(Status.REJECT); - } - } - - /** - * ACCEPT the associated message if the callback is not null. - * @param ackCallback the callback. - */ - public static void accept(@Nullable AcknowledgmentCallback ackCallback) { - if (ackCallback != null) { - ackCallback.acknowledge(Status.ACCEPT); - } - } - - /** - * REJECT the associated message if the callback is not null. - * @param ackCallback the callback. - */ - public static void reject(@Nullable AcknowledgmentCallback ackCallback) { - if (ackCallback != null) { - ackCallback.acknowledge(Status.REJECT); - } - } - - /** - * REQUEUE the associated message if the callback is not null. - * @param ackCallback the callback. - */ - public static void requeue(@Nullable AcknowledgmentCallback ackCallback) { - if (ackCallback != null) { - ackCallback.acknowledge(Status.REQUEUE); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/acks/AcknowledgmentCallback.java b/spring-integration-core/src/main/java/org/springframework/integration/acks/AcknowledgmentCallback.java deleted file mode 100644 index 605dcbb6adb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/acks/AcknowledgmentCallback.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.acks; - -/** - * A general abstraction over acknowledgments. - * - * @author Gary Russell - * - * @since 5.0.1 - * - */ -@FunctionalInterface -public interface AcknowledgmentCallback extends SimpleAcknowledgment { - - /** - * Acknowledge the message. - * @param status the status. - */ - void acknowledge(Status status); - - @Override - default void acknowledge() { - acknowledge(Status.ACCEPT); - } - - /** - * Implementations must implement this to indicate when the ack has been - * processed by the user so that the framework can auto-ack if needed. - * @return true if the message is already acknowledged. - */ - default boolean isAcknowledged() { - return false; - } - - /** - * Disable auto acknowledgment by a {@code SourcePollingChannelAdapter} - * or {@code MessageSourcePollingTemplate}. Not all implementations support - * this - for example, the Kafka message source. - */ - default void noAutoAck() { - throw new UnsupportedOperationException("You cannot disable auto acknowledgment with this implementation"); - } - - /** - * Return true if this acknowledgment supports auto ack when it has not been - * already ack'd by the application. - * @return true if auto ack is supported. - */ - default boolean isAutoAck() { - return true; - } - - enum Status { - - /** - * Mark the message as accepted. - */ - ACCEPT, - - /** - * Mark the message as rejected. - */ - REJECT, - - /** - * Reject the message and requeue so that it will be redelivered. - */ - REQUEUE - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/acks/AcknowledgmentCallbackFactory.java b/spring-integration-core/src/main/java/org/springframework/integration/acks/AcknowledgmentCallbackFactory.java deleted file mode 100644 index 16c0aabf2c6..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/acks/AcknowledgmentCallbackFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.acks; - -/** - * A factory for creating {@link AcknowledgmentCallback}s. - * - * @param a type containing information with which to populate the acknowledgment. - * - * @author Gary Russell - * - * @since 5.0.1 - * - */ -@FunctionalInterface -public interface AcknowledgmentCallbackFactory { - - /** - * Create the callback. - * @param info information for the callback to process the acknowledgment. - * @return the callback - */ - AcknowledgmentCallback createCallback(T info); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/acks/SimpleAcknowledgment.java b/spring-integration-core/src/main/java/org/springframework/integration/acks/SimpleAcknowledgment.java deleted file mode 100644 index 8171e60d109..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/acks/SimpleAcknowledgment.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.acks; - -/** - * Opaque object for manually acknowledging. - * - * @author Gary Russell - * @since 5.3 - * - */ -@FunctionalInterface -public interface SimpleAcknowledgment { - - /** - * Acknowledge the message delivery. - */ - void acknowledge(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/acks/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/acks/package-info.java deleted file mode 100644 index c263244d02d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/acks/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to message acknowledgment. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.acks; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java deleted file mode 100644 index e75755ad006..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractAggregatingMessageGroupProcessor.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Map; -import java.util.function.Function; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.DefaultMessageBuilderFactory; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Base class for MessageGroupProcessor implementations that aggregate the group of Messages into a single Message. - * - * @author Iwein Fuld - * @author Alexander Peters - * @author Mark Fisher - * @author Dave Syer - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -public abstract class AbstractAggregatingMessageGroupProcessor implements MessageGroupProcessor, - BeanFactoryAware { - - protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - final - - private Function> headersFunction = new DefaultAggregateHeadersFunction(); - - private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - private boolean messageBuilderFactorySet; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /** - * Specify a {@link Function} to map {@link MessageGroup} into composed headers for output message. - * @param headersFunction the {@link Function} to use. - * @since 5.2 - */ - public void setHeadersFunction(Function> headersFunction) { - Assert.notNull(headersFunction, "'headersFunction' must not be null"); - this.headersFunction = headersFunction; - } - - protected Function> getHeadersFunction() { - return this.headersFunction; - } - - protected MessageBuilderFactory getMessageBuilderFactory() { - if (!this.messageBuilderFactorySet) { - if (this.beanFactory != null) { - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - } - this.messageBuilderFactorySet = true; - } - return this.messageBuilderFactory; - } - - @Override - public final Object processMessageGroup(MessageGroup group) { - Assert.notNull(group, "MessageGroup must not be null"); - Map headers = aggregateHeaders(group); - Object payload = aggregatePayloads(group, headers); - AbstractIntegrationMessageBuilder builder; - if (payload instanceof Message) { - builder = getMessageBuilderFactory().fromMessage((Message) payload); - } - else if (payload instanceof AbstractIntegrationMessageBuilder) { - builder = (AbstractIntegrationMessageBuilder) payload; - } - else { - builder = getMessageBuilderFactory().withPayload(payload); - } - - return builder.copyHeadersIfAbsent(headers); - } - - /** - * This default implementation simply returns all headers that have no conflicts among the group. An absent header - * on one or more Messages within the group is not considered a conflict. Subclasses may override this method with - * more advanced conflict-resolution strategies if necessary. - * @param group The message group. - * @return The aggregated headers. - */ - protected Map aggregateHeaders(MessageGroup group) { - return getHeadersFunction().apply(group); - } - - protected abstract Object aggregatePayloads(MessageGroup group, Map defaultHeaders); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java deleted file mode 100644 index 19b097ef17f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.locks.Lock; -import java.util.function.BiFunction; - -import org.aopalliance.aop.Advice; -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.Lifecycle; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.handler.DiscardingMessageHandler; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.store.SimpleMessageGroup; -import org.springframework.integration.store.SimpleMessageStore; -import org.springframework.integration.store.UniqueExpiryCallback; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.locks.DefaultLockRegistry; -import org.springframework.integration.support.locks.LockRegistry; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.integration.util.UUIDConverter; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.core.DestinationResolutionException; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; - -/** - * Abstract Message handler that holds a buffer of correlated messages in a - * {@link org.springframework.integration.store.MessageStore}. - * This class takes care of correlated groups of messages - * that can be completed in batches. It is useful for custom implementation of - * MessageHandlers that require correlation and is used as a base class for Aggregator - - * {@link AggregatingMessageHandler} and Resequencer - {@link ResequencingMessageHandler}, - * or custom implementations requiring correlation. - *

- * To customize this handler inject {@link CorrelationStrategy}, - * {@link ReleaseStrategy}, and {@link MessageGroupProcessor} implementations as - * you require. - *

- * By default, the {@link CorrelationStrategy} will be a - * {@link HeaderAttributeCorrelationStrategy} and the {@link ReleaseStrategy} will be a - * {@link SequenceSizeReleaseStrategy}. - *

- * Use proper {@link CorrelationStrategy} for cases when same - * {@link org.springframework.integration.store.MessageStore} is used - * for multiple handlers to ensure uniqueness of message groups across handlers. - *

- * When the {@link #expireTimeout} is greater than 0, groups which are older than this timeout - * are purged from the store on start up (or when {@link #purgeOrphanedGroups()} is called). - * If {@link #expireDuration} is provided, the task is scheduled to perform - * {@link #purgeOrphanedGroups()} periodically. - * - * @author Iwein Fuld - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author David Liu - * @author Enrique Rodriguez - * @author Meherzad Lahewala - * @author Jayadev Sirimamilla - * @author Ngoc Nhan - * - * @since 2.0 - */ -public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageProducingHandler - implements DiscardingMessageHandler, ApplicationEventPublisherAware, ManageableLifecycle { - - private final Comparator> sequenceNumberComparator = new MessageSequenceComparator(); - - private final Map> expireGroupScheduledFutures = new ConcurrentHashMap<>(); - - private MessageGroupProcessor outputProcessor; - - @SuppressWarnings("NullAway.Init") - private MessageGroupStore messageStore; - - private CorrelationStrategy correlationStrategy; - - private ReleaseStrategy releaseStrategy; - - private boolean releaseStrategySet; - - @Nullable - private MessageChannel discardChannel; - - @Nullable - private String discardChannelName; - - private boolean sendPartialResultOnExpiry; - - private boolean discardIndividuallyOnExpiry = true; - - private boolean sequenceAware; - - private LockRegistry lockRegistry = new DefaultLockRegistry(); - - private boolean lockRegistrySet = false; - - private long minimumTimeoutForEmptyGroups; - - private boolean releasePartialSequences; - - @Nullable - private Expression groupTimeoutExpression; - - @Nullable - private List forceReleaseAdviceChain; - - private long expireTimeout; - - @Nullable - private Duration expireDuration; - - private MessageGroupProcessor forceReleaseProcessor = new ForceReleaseMessageGroupProcessor(); - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - @Nullable - private ApplicationEventPublisher applicationEventPublisher; - - private boolean expireGroupsUponTimeout = true; - - private boolean popSequence = true; - - private boolean releaseLockBeforeSend; - - private volatile boolean running; - - @Nullable - private BiFunction, String, String> groupConditionSupplier; - - public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store, - @Nullable CorrelationStrategy correlationStrategy, @Nullable ReleaseStrategy releaseStrategy) { - - Assert.notNull(processor, "'processor' must not be null"); - Assert.notNull(store, "'store' must not be null"); - - setMessageStore(store); - this.outputProcessor = processor; - - this.correlationStrategy = - correlationStrategy == null - ? new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID) - : correlationStrategy; - - this.releaseStrategy = - releaseStrategy == null - ? new SimpleSequenceSizeReleaseStrategy() - : releaseStrategy; - - this.releaseStrategySet = releaseStrategy != null; - this.sequenceAware = this.releaseStrategy instanceof SequenceSizeReleaseStrategy; - } - - public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) { - this(processor, store, null, null); - } - - public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor) { - this(processor, new SimpleMessageStore(0), null, null); - } - - public void setLockRegistry(LockRegistry lockRegistry) { - Assert.isTrue(!this.lockRegistrySet, "'this.lockRegistry' can not be reset once its been set"); - Assert.notNull(lockRegistry, "'lockRegistry' must not be null"); - this.lockRegistry = lockRegistry; - this.lockRegistrySet = true; - } - - public final void setMessageStore(MessageGroupStore store) { - this.messageStore = store; - UniqueExpiryCallback expiryCallback = - (messageGroupStore, group) -> this.forceReleaseProcessor.processMessageGroup(group); - store.registerMessageGroupExpiryCallback(expiryCallback); - } - - public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) { - Assert.notNull(correlationStrategy, "'correlationStrategy' must not be null"); - this.correlationStrategy = correlationStrategy; - } - - public void setReleaseStrategy(ReleaseStrategy releaseStrategy) { - Assert.notNull(releaseStrategy, "'releaseStrategy' must not be null"); - this.releaseStrategy = releaseStrategy; - this.sequenceAware = this.releaseStrategy instanceof SequenceSizeReleaseStrategy; - this.releaseStrategySet = true; - } - - public void setGroupTimeoutExpression(Expression groupTimeoutExpression) { - this.groupTimeoutExpression = groupTimeoutExpression; - } - - public void setForceReleaseAdviceChain(List forceReleaseAdviceChain) { - Assert.notNull(forceReleaseAdviceChain, "'forceReleaseAdviceChain' must not be null"); - this.forceReleaseAdviceChain = forceReleaseAdviceChain; - } - - /** - * Specify a {@link MessageGroupProcessor} for the output function. - * @param outputProcessor the {@link MessageGroupProcessor} to use - * @since 5.0 - */ - public void setOutputProcessor(MessageGroupProcessor outputProcessor) { - Assert.notNull(outputProcessor, "'processor' must not be null"); - this.outputProcessor = outputProcessor; - } - - /** - * Return a configured {@link MessageGroupProcessor}. - * @return the configured {@link MessageGroupProcessor} - * @since 5.2 - */ - public MessageGroupProcessor getOutputProcessor() { - return this.outputProcessor; - } - - public void setDiscardChannel(MessageChannel discardChannel) { - Assert.notNull(discardChannel, "'discardChannel' cannot be null"); - this.discardChannel = discardChannel; - } - - public void setDiscardChannelName(String discardChannelName) { - Assert.hasText(discardChannelName, "'discardChannelName' must not be empty"); - this.discardChannelName = discardChannelName; - } - - public void setSendPartialResultOnExpiry(boolean sendPartialResultOnExpiry) { - this.sendPartialResultOnExpiry = sendPartialResultOnExpiry; - } - - /** - * Set to {@code false} to send to discard channel a whole expired group as a single message. - * This option makes sense only if {@link #sendPartialResultOnExpiry} is set to {@code false} (default). - * And also if {@link #discardChannel} is injected. - * @param discardIndividuallyOnExpiry false to discard the whole group as one message. - * @since 6.5 - * @see #sendPartialResultOnExpiry - */ - public void setDiscardIndividuallyOnExpiry(boolean discardIndividuallyOnExpiry) { - this.discardIndividuallyOnExpiry = discardIndividuallyOnExpiry; - } - - /** - * By default, when a MessageGroupStoreReaper is configured to expire partial - * groups, empty groups are also removed. Empty groups exist after a group - * is released normally. This is to enable the detection and discarding of - * late-arriving messages. If you wish to expire empty groups on a longer - * schedule than expiring partial groups, set this property. Empty groups will - * then not be removed from the MessageStore until they have not been modified - * for at least this number of milliseconds. - * @param minimumTimeoutForEmptyGroups The minimum timeout. - */ - public void setMinimumTimeoutForEmptyGroups(long minimumTimeoutForEmptyGroups) { - this.minimumTimeoutForEmptyGroups = minimumTimeoutForEmptyGroups; - } - - /** - * Set {@code releasePartialSequences} on an underlying default - * {@link SequenceSizeReleaseStrategy}. Ignored for other release strategies. - * @param releasePartialSequences true to allow release. - */ - public void setReleasePartialSequences(boolean releasePartialSequences) { - if (!this.releaseStrategySet && releasePartialSequences) { - setReleaseStrategy(new SequenceSizeReleaseStrategy(releasePartialSequences)); - } - this.releasePartialSequences = releasePartialSequences; - } - - /** - * Expire (completely remove) a group if it is completed due to timeout. - * Default true - * @param expireGroupsUponTimeout the expireGroupsUponTimeout to set - * @since 4.1 - */ - public void setExpireGroupsUponTimeout(boolean expireGroupsUponTimeout) { - this.expireGroupsUponTimeout = expireGroupsUponTimeout; - } - - /** - * Perform a - * {@link org.springframework.integration.support.MessageBuilder#popSequenceDetails()} - * for output message or not. Default to true. This option removes the sequence - * information added by the nearest upstream component with {@code applySequence=true} - * (for example splitter). - * @param popSequence the boolean flag to use. - * @since 5.1 - */ - public void setPopSequence(boolean popSequence) { - this.popSequence = popSequence; - } - - protected boolean isReleaseLockBeforeSend() { - return this.releaseLockBeforeSend; - } - - /** - * Set to true to release the message group lock before sending any output. See - * "Avoiding Deadlocks" in the Aggregator section of the reference manual for more - * information as to why this might be needed. - * @param releaseLockBeforeSend true to release the lock. - * @since 5.1.1 - */ - public void setReleaseLockBeforeSend(boolean releaseLockBeforeSend) { - this.releaseLockBeforeSend = releaseLockBeforeSend; - } - - /** - * Configure a timeout in milliseconds for purging old orphaned groups from the store. - * Used on startup and when an {@link #expireDuration} is provided, the task for running - * {@link #purgeOrphanedGroups()} is scheduled with that period. - * The {@link #forceReleaseProcessor} is used to process those expired groups according - * the "force complete" options. A group can be orphaned if a persistent message group - * store is used and no new messages arrive for that group after a restart. - * @param expireTimeout the number of milliseconds to determine old orphaned groups in the store to purge. - * @since 5.4 - * @see #purgeOrphanedGroups() - */ - public void setExpireTimeout(long expireTimeout) { - Assert.isTrue(expireTimeout > 0, "'expireTimeout' must be more than 0."); - this.expireTimeout = expireTimeout; - } - - /** - * Configure a {@link Duration} (in millis) how often to clean up old orphaned groups from the store. - * @param expireDuration the delay how often to call {@link #purgeOrphanedGroups()}. - * @since 5.4 - * @see #purgeOrphanedGroups() - * @see #setExpireDuration(Duration) - * @see #setExpireTimeout(long) - */ - public void setExpireDurationMillis(long expireDuration) { - setExpireDuration(Duration.ofMillis(expireDuration)); - } - - /** - * Configure a {@link Duration} how often to clean up old orphaned groups from the store. - * @param expireDuration the delay how often to call {@link #purgeOrphanedGroups()}. - * @since 5.4 - * @see #purgeOrphanedGroups() - * @see #setExpireTimeout(long) - */ - public void setExpireDuration(@Nullable Duration expireDuration) { - this.expireDuration = expireDuration; - } - - /** - * Configure a {@link BiFunction} to supply a group condition from a message to be added to the group. - * The {@code null} result from the function will reset a condition set before. - * @param conditionSupplier the function to supply a group condition from a message to be added to the group. - * @since 5.5 - * @see GroupConditionProvider - */ - public void setGroupConditionSupplier(BiFunction, String, String> conditionSupplier) { - this.groupConditionSupplier = conditionSupplier; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - protected void onInit() { - super.onInit(); - Assert.state(!(this.discardChannelName != null && this.discardChannel != null), - "'discardChannelName' and 'discardChannel' are mutually exclusive."); - BeanFactory beanFactory = getBeanFactory(); - if (beanFactory != null) { - if (this.outputProcessor instanceof BeanFactoryAware beanFactoryAware) { - beanFactoryAware.setBeanFactory(beanFactory); - } - if (this.correlationStrategy instanceof BeanFactoryAware beanFactoryAware) { - beanFactoryAware.setBeanFactory(beanFactory); - } - if (this.releaseStrategy instanceof BeanFactoryAware beanFactoryAware) { - beanFactoryAware.setBeanFactory(beanFactory); - } - } - - if (this.releasePartialSequences) { - Assert.isInstanceOf(SequenceSizeReleaseStrategy.class, this.releaseStrategy, () -> - "Release strategy of type [" + this.releaseStrategy.getClass().getSimpleName() + - "] cannot release partial sequences. Use a SequenceSizeReleaseStrategy instead."); - ((SequenceSizeReleaseStrategy) this.releaseStrategy) - .setReleasePartialSequences(this.releasePartialSequences); - } - - if (this.evaluationContext == null) { - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - } - - if (this.sequenceAware) { - this.logger.warn("Using a SequenceSizeReleaseStrategy with large groups may not perform well, consider " - + "using a SimpleSequenceSizeReleaseStrategy"); - } - - /* - * Disallow any further changes to the lock registry - * (checked in the setter). - */ - this.lockRegistrySet = true; - this.forceReleaseProcessor = createGroupTimeoutProcessor(); - - if (this.releaseStrategy instanceof GroupConditionProvider groupConditionProvider) { - this.groupConditionSupplier = groupConditionProvider.getGroupConditionSupplier(); - } - } - - private MessageGroupProcessor createGroupTimeoutProcessor() { - MessageGroupProcessor processor = new ForceReleaseMessageGroupProcessor(); - - if (this.groupTimeoutExpression != null && !CollectionUtils.isEmpty(this.forceReleaseAdviceChain)) { - ProxyFactory proxyFactory = new ProxyFactory(processor); - this.forceReleaseAdviceChain.forEach(proxyFactory::addAdvice); - return (MessageGroupProcessor) proxyFactory.getProxy(getApplicationContext().getClassLoader()); - } - return processor; - } - - @Override - public String getComponentType() { - return "aggregator"; - } - - public MessageGroupStore getMessageStore() { - return this.messageStore; - } - - protected Map> getExpireGroupScheduledFutures() { - return this.expireGroupScheduledFutures; - } - - protected CorrelationStrategy getCorrelationStrategy() { - return this.correlationStrategy; - } - - protected ReleaseStrategy getReleaseStrategy() { - return this.releaseStrategy; - } - - @Nullable - protected BiFunction, String, String> getGroupConditionSupplier() { - return this.groupConditionSupplier; - } - - @Override - @Nullable - public MessageChannel getDiscardChannel() { - String channelName = this.discardChannelName; - if (channelName == null && this.discardChannel == null) { - channelName = IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME; - } - if (channelName != null) { - try { - this.discardChannel = getChannelResolver().resolveDestination(channelName); - } - catch (DestinationResolutionException ex) { - if (channelName.equals(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME)) { - this.discardChannel = new NullChannel(); - } - else { - throw ex; - } - } - this.discardChannelName = null; - } - return this.discardChannel; - } - - @Nullable - protected String getDiscardChannelName() { - return this.discardChannelName; - } - - protected boolean isSendPartialResultOnExpiry() { - return this.sendPartialResultOnExpiry; - } - - protected boolean isSequenceAware() { - return this.sequenceAware; - } - - protected LockRegistry getLockRegistry() { - return this.lockRegistry; - } - - protected boolean isLockRegistrySet() { - return this.lockRegistrySet; - } - - protected long getMinimumTimeoutForEmptyGroups() { - return this.minimumTimeoutForEmptyGroups; - } - - protected boolean isReleasePartialSequences() { - return this.releasePartialSequences; - } - - @Nullable - protected Expression getGroupTimeoutExpression() { - return this.groupTimeoutExpression; - } - - protected EvaluationContext getEvaluationContext() { - return this.evaluationContext; - } - - @Override - protected void handleMessageInternal(Message message) { - Object correlationKey = this.correlationStrategy.getCorrelationKey(message); - Assert.state(correlationKey != null, - "Null correlation not allowed. Maybe the CorrelationStrategy is failing?"); - - this.logger.debug(() -> "Handling message with correlationKey [" + correlationKey + "]: " + message); - - UUID groupIdUuid = UUIDConverter.getUUID(correlationKey); - Lock lock = this.lockRegistry.obtain(groupIdUuid.toString()); - - boolean noOutput = true; - try { - lock.lockInterruptibly(); - try { - noOutput = processMessageForGroup(message, correlationKey, groupIdUuid, lock); - } - finally { - if (noOutput || !this.releaseLockBeforeSend) { - lock.unlock(); - } - } - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new MessageHandlingException(message, "Interrupted getting lock in the [" + this + ']', e); - } - } - - private boolean processMessageForGroup(Message message, Object correlationKey, UUID groupIdUuid, Lock lock) { - boolean noOutput = true; - cancelScheduledFutureIfAny(correlationKey, groupIdUuid, true); - MessageGroup messageGroup = this.messageStore.getMessageGroup(correlationKey); - if (this.sequenceAware) { - messageGroup = new SequenceAwareMessageGroup(messageGroup); - } - - if (!messageGroup.isComplete() && messageGroup.canAdd(message)) { - MessageGroup messageGroupToLog = messageGroup; - this.logger.trace(() -> "Adding message to group [ " + messageGroupToLog + "]"); - messageGroup = store(correlationKey, message); - - messageGroup = setGroupConditionIfAny(message, messageGroup); - - if (this.releaseStrategy.canRelease(messageGroup)) { - Collection> completedMessages = null; - try { - noOutput = false; - completedMessages = completeGroup(message, correlationKey, messageGroup, lock); - } - finally { - // Possible clean (implementation dependency) up - // even if there was an exception processing messages - afterRelease(messageGroup, completedMessages); - } - if (!isExpireGroupsUponCompletion() && this.minimumTimeoutForEmptyGroups > 0) { - removeEmptyGroupAfterTimeout(groupIdUuid, this.minimumTimeoutForEmptyGroups); - } - } - else { - scheduleGroupToForceComplete(messageGroup); - } - } - else { - noOutput = false; - discardMessage(message, lock); - } - return noOutput; - } - - private void cancelScheduledFutureIfAny(Object correlationKey, UUID groupIdUuid, boolean mayInterruptIfRunning) { - ScheduledFuture scheduledFuture = this.expireGroupScheduledFutures.remove(groupIdUuid); - if (scheduledFuture != null) { - boolean canceled = scheduledFuture.cancel(mayInterruptIfRunning); - if (canceled) { - this.logger.debug(() -> - "Cancel 'ScheduledFuture' for MessageGroup with Correlation Key [ " + correlationKey + "]."); - } - } - } - - private MessageGroup setGroupConditionIfAny(Message message, MessageGroup messageGroup) { - MessageGroup messageGroupToUse = messageGroup; - - if (this.groupConditionSupplier != null) { - String condition = this.groupConditionSupplier.apply(message, messageGroupToUse.getCondition()); - this.messageStore.setGroupCondition(messageGroupToUse.getGroupId(), condition); - messageGroupToUse = this.messageStore.getMessageGroup(messageGroupToUse.getGroupId()); - if (this.sequenceAware) { - messageGroupToUse = new SequenceAwareMessageGroup(messageGroupToUse); - } - } - - return messageGroupToUse; - } - - protected boolean isExpireGroupsUponCompletion() { - return false; - } - - private void removeEmptyGroupAfterTimeout(UUID groupId, long timeout) { - ScheduledFuture scheduledFuture = - getTaskScheduler() - .schedule(() -> { - Lock lock = this.lockRegistry.obtain(groupId.toString()); - - try { - lock.lockInterruptibly(); - try { - this.expireGroupScheduledFutures.remove(groupId); - /* - * Obtain a fresh state for group from the MessageStore, - * since it could be changed while we have waited for lock. - */ - MessageGroup groupNow = this.messageStore.getMessageGroup(groupId); - boolean removeGroup = groupNow.size() == 0 && - groupNow.getLastModified() - <= (System.currentTimeMillis() - this.minimumTimeoutForEmptyGroups); - if (removeGroup) { - this.logger.debug(() -> "Removing empty group: " + groupId); - remove(groupNow); - } - } - finally { - lock.unlock(); - } - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - this.logger.debug(() -> "Thread was interrupted while trying to obtain lock." - + "Rescheduling empty MessageGroup [ " + groupId + "] for removal."); - removeEmptyGroupAfterTimeout(groupId, timeout); - } - - }, Instant.now().plusMillis(timeout)); - - this.logger.debug(() -> "Schedule empty MessageGroup [ " + groupId + "] for removal."); - this.expireGroupScheduledFutures.put(groupId, scheduledFuture); - } - - private void scheduleGroupToForceComplete(MessageGroup messageGroup) { - Object groupTimeout = obtainGroupTimeout(messageGroup); - /* - * When 'groupTimeout' is evaluated to 'null' we do nothing. - * The 'MessageGroupStoreReaper' can be used to 'forceComplete' message groups. - */ - if (groupTimeout != null) { - Date startTime = null; - if (groupTimeout instanceof Date date) { - startTime = date; - } - else if ((Long) groupTimeout > 0) { - startTime = new Date(System.currentTimeMillis() + (Long) groupTimeout); - } - - if (startTime != null) { - Object groupId = messageGroup.getGroupId(); - long timestamp = messageGroup.getTimestamp(); - long lastModified = messageGroup.getLastModified(); - ScheduledFuture scheduledFuture = - getTaskScheduler() - .schedule(() -> { - try { - processForceRelease(groupId, timestamp, lastModified); - } - catch (MessageDeliveryException ex) { - logger.warn(ex, () -> - "The MessageGroup [" + groupId + - "] is rescheduled by the reason of: "); - scheduleGroupToForceComplete(groupId); - } - }, startTime.toInstant()); - - this.logger.debug(() -> "Schedule MessageGroup [ " + messageGroup + "] to 'forceComplete'."); - this.expireGroupScheduledFutures.put(UUIDConverter.getUUID(groupId), scheduledFuture); - } - else { - this.forceReleaseProcessor.processMessageGroup(messageGroup); - } - } - } - - private void scheduleGroupToForceComplete(Object groupId) { - MessageGroup messageGroup = this.messageStore.getMessageGroup(groupId); - scheduleGroupToForceComplete(messageGroup); - } - - private void processForceRelease(Object groupId, long timestamp, long lastModified) { - MessageGroup messageGroup = this.messageStore.getMessageGroup(groupId); - if (messageGroup.getTimestamp() == timestamp && messageGroup.getLastModified() == lastModified) { - this.forceReleaseProcessor.processMessageGroup(messageGroup); - } - } - - private void discardMessage(Message message, Lock lock) { - if (this.releaseLockBeforeSend) { - lock.unlock(); - } - discardMessage(message); - } - - private void discardMessage(Message message) { - MessageChannel messageChannel = getDiscardChannel(); - if (messageChannel != null) { - this.messagingTemplate.send(messageChannel, message); - } - } - - /** - * Allows you to provide additional logic that needs to be performed after the MessageGroup was released. - * @param group The group. - * @param completedMessages The completed messages. - */ - protected abstract void afterRelease(MessageGroup group, @Nullable Collection> completedMessages); - - /** - * Subclasses may override if special action is needed because the group was released or discarded - * due to a timeout. By default, {@link #afterRelease(MessageGroup, Collection)} is invoked. - * @param group The group. - * @param completedMessages The completed messages. - * @param timeout True if the release/discard was due to a timeout. - */ - protected void afterRelease(MessageGroup group, Collection> completedMessages, boolean timeout) { - afterRelease(group, completedMessages); - } - - protected void forceComplete(MessageGroup group) { // NOSONAR Complexity - Object correlationKey = group.getGroupId(); - // UUIDConverter is no-op if already converted - UUID groupId = UUIDConverter.getUUID(correlationKey); - Lock lock = this.lockRegistry.obtain(groupId.toString()); - boolean removeGroup = true; - boolean noOutput = true; - try { - lock.lockInterruptibly(); - try { - cancelScheduledFutureIfAny(correlationKey, groupId, false); - MessageGroup groupNow = group; - /* - * If the group argument is not already complete, - * re-fetch it because it might have changed while we were waiting on - * its lock. If the last modified timestamp changed, defer the completion - * because the selection condition may have changed such that the group - * would no longer be eligible. If the timestamp changed, it's a completely new - * group and should not be reaped on this cycle. - * - * If the group argument is already complete, do not re-fetch. - * Note: not all message stores provide a direct reference to its internal - * group so the initial 'isComplete()` will only return true for those stores if - * the group was already complete at the time of its selection as a candidate. - * - * If the group is marked complete, only consider it - * for reaping if it's empty (and both timestamps are unaltered). - */ - if (!group.isComplete()) { - groupNow = this.messageStore.getMessageGroup(correlationKey); - } - long lastModifiedNow = groupNow.getLastModified(); - int groupSize = groupNow.size(); - - if ((!groupNow.isComplete() || groupSize == 0) - && group.getLastModified() == lastModifiedNow - && group.getTimestamp() == groupNow.getTimestamp()) { - - if (groupSize > 0) { - noOutput = false; - if (this.releaseStrategy.canRelease(groupNow)) { - completeGroup(correlationKey, groupNow, lock); - } - else { - expireGroup(correlationKey, groupNow, lock); - } - if (!this.expireGroupsUponTimeout) { - afterRelease(groupNow, groupNow.getMessages(), true); - removeGroup = false; - } - } - else { - /* - * By default, empty groups are removed on the same schedule as non-empty - * groups. A longer timeout for empty groups can be enabled by - * setting minimumTimeoutForEmptyGroups. - */ - removeGroup = - lastModifiedNow <= (System.currentTimeMillis() - this.minimumTimeoutForEmptyGroups); - if (removeGroup) { - this.logger.debug(() -> "Removing empty group: " + correlationKey); - } - } - } - else { - removeGroup = false; - this.logger.debug(() -> "Group expiry candidate (" + correlationKey + - ") has changed - it may be reconsidered for a future expiration"); - } - } - catch (MessageDeliveryException e) { - removeGroup = false; - this.logger.debug(() -> "Group expiry candidate (" + correlationKey + - ") has been affected by MessageDeliveryException - " + - "it may be reconsidered for a future expiration one more time"); - throw e; - } - finally { - try { - if (removeGroup) { - remove(group); - } - } - finally { - if (noOutput || !this.releaseLockBeforeSend) { - lock.unlock(); - } - } - } - } - catch (@SuppressWarnings("unused") InterruptedException ie) { - Thread.currentThread().interrupt(); - this.logger.debug("Thread was interrupted while trying to obtain lock"); - } - } - - protected void remove(MessageGroup group) { - Object correlationKey = group.getGroupId(); - this.messageStore.removeMessageGroup(correlationKey); - } - - protected int findLastReleasedSequenceNumber(@SuppressWarnings("unused") Object groupId, - Collection> partialSequence) { - - Message lastReleasedMessage = Collections.max(partialSequence, this.sequenceNumberComparator); - return StaticMessageHeaderAccessor.getSequenceNumber(lastReleasedMessage); - } - - protected MessageGroup store(Object correlationKey, Message message) { - return this.messageStore.addMessageToGroup(correlationKey, message); - } - - protected void expireGroup(Object correlationKey, MessageGroup group, Lock lock) { - this.logger.debug(() -> "Expiring MessageGroup with correlationKey[" + correlationKey + "]"); - if (this.sendPartialResultOnExpiry) { - this.logger.debug(() -> "Prematurely releasing partially complete group with key [" - + correlationKey + "] to: " + getOutputChannel()); - completeGroup(correlationKey, group, lock); - } - else { - this.logger.debug(() -> "Discarding messages of partially complete group with key [" - + correlationKey + "] to: " - + (this.discardChannelName != null ? this.discardChannelName : this.discardChannel)); - if (this.releaseLockBeforeSend) { - lock.unlock(); - } - MessageChannel messageChannel = getDiscardChannel(); - if (messageChannel != null) { - if (this.discardIndividuallyOnExpiry) { - group.getMessages() - .forEach(this::discardMessage); - } - else { - List> messagesInGroupToDiscard = new ArrayList<>(group.getMessages()); - discardMessage(new GenericMessage<>(messagesInGroupToDiscard)); - } - } - } - if (this.applicationEventPublisher != null) { - this.applicationEventPublisher.publishEvent( - new MessageGroupExpiredEvent(this, correlationKey, group.size(), - new Date(group.getLastModified()), new Date(), !this.sendPartialResultOnExpiry)); - } - } - - @SuppressWarnings("NullAway") // Never called with an empty group - protected void completeGroup(Object correlationKey, MessageGroup group, Lock lock) { - completeGroup(group.getOne(), correlationKey, group, lock); - } - - @SuppressWarnings("unchecked") - @Nullable - protected Collection> completeGroup(Message message, Object correlationKey, MessageGroup group, - Lock lock) { - - Collection> partialSequence = null; - Object result; - try { - this.logger.debug(() -> "Completing group with correlationKey [" + correlationKey + "]"); - - result = this.outputProcessor.processMessageGroup(group); - Assert.state(result != null, "The processorMessageGroup returned a null result. Null result is not expected."); - if (isResultCollectionOfMessages(result)) { - partialSequence = (Collection>) result; - } - - if (this.popSequence && partialSequence == null) { - AbstractIntegrationMessageBuilder messageBuilder = null; - if (result instanceof AbstractIntegrationMessageBuilder) { - messageBuilder = (AbstractIntegrationMessageBuilder) result; - } - else if (!(result instanceof Message)) { - messageBuilder = - getMessageBuilderFactory() - .withPayload(result) - .copyHeaders(message.getHeaders()); - } - else if (compareSequences((Message) result, message)) { - messageBuilder = - getMessageBuilderFactory() - .fromMessage((Message) result); - } - result = messageBuilder != null ? messageBuilder.popSequenceDetails() : result; - } - } - finally { - if (this.releaseLockBeforeSend) { - lock.unlock(); - } - } - sendOutputs(result, message); - return partialSequence; - } - - private static boolean compareSequences(Message msg1, Message msg2) { - Object sequence1 = msg1.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS); - Object sequence2 = msg2.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS); - return ObjectUtils.nullSafeEquals(sequence1, sequence2); - - } - - private static boolean isResultCollectionOfMessages(Object result) { - if (result instanceof Collection resultCollection) { - Class commonElementType = CollectionUtils.findCommonElementType(resultCollection); - return commonElementType != null && Message.class.isAssignableFrom(commonElementType); - } - return false; - } - - @Nullable - protected Object obtainGroupTimeout(MessageGroup group) { - if (this.groupTimeoutExpression != null) { - Object timeout = this.groupTimeoutExpression.getValue(this.evaluationContext, group); - if (timeout instanceof Date) { - return timeout; - } - else if (timeout != null) { - try { - return Long.parseLong(timeout.toString()); - } - catch (NumberFormatException ex) { - throw new IllegalStateException("Error evaluating 'groupTimeoutExpression'", ex); - } - } - } - return null; - } - - @Override - public void destroy() { - this.expireGroupScheduledFutures.values().forEach(future -> future.cancel(true)); - } - - @Override - public void start() { - if (!this.running) { - this.running = true; - if (this.outputProcessor instanceof Lifecycle lifecycle) { - lifecycle.start(); - } - if (this.releaseStrategy instanceof Lifecycle lifecycle) { - lifecycle.start(); - } - if (this.expireTimeout > 0) { - purgeOrphanedGroups(); - if (this.expireDuration != null) { - getTaskScheduler() - .scheduleWithFixedDelay(this::purgeOrphanedGroups, this.expireDuration); - } - } - } - } - - @Override - public void stop() { - if (this.running) { - this.running = false; - if (this.outputProcessor instanceof Lifecycle lifecycle) { - lifecycle.stop(); - } - if (this.releaseStrategy instanceof Lifecycle lifecycle) { - lifecycle.stop(); - } - } - } - - @Override - public boolean isRunning() { - return this.running; - } - - /** - * Perform a {@link MessageGroupStore#expireMessageGroups(long)} with the provided {@link #expireTimeout}. - * Can be called externally at any time. - * Internally it is called from the scheduled task with the configured {@link #expireDuration}. - * @since 5.4 - */ - public void purgeOrphanedGroups() { - Assert.isTrue(this.expireTimeout > 0, "'expireTimeout' must be more than 0."); - this.messageStore.expireMessageGroups(this.expireTimeout); - } - - protected static class SequenceAwareMessageGroup extends SimpleMessageGroup { - - @Nullable - private final SimpleMessageGroup sourceGroup; - - public SequenceAwareMessageGroup(MessageGroup messageGroup) { - /* - * Since this group is temporary, and never added to, we simply use the - * supplied group's message collection for the lookup rather than creating a - * new group. - */ - super(messageGroup.getMessages(), null, messageGroup.getGroupId(), messageGroup.getTimestamp(), - messageGroup.isComplete(), true); - if (messageGroup instanceof SimpleMessageGroup simpleMessageGroup) { - this.sourceGroup = simpleMessageGroup; - } - else { - this.sourceGroup = null; - } - } - - /** - * This method determines whether messages have been added to this group that - * supersede the given message based on its sequence id. This can be helpful to - * avoid ending up with sequences larger than their required sequence size or - * sequences that are missing certain sequence numbers. - */ - @Override - public boolean canAdd(Message message) { - if (this.size() == 0) { - return true; - } - Integer messageSequenceNumber = message.getHeaders() - .get(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, Integer.class); - if (messageSequenceNumber != null && messageSequenceNumber > 0) { - Integer messageSequenceSize = message.getHeaders() - .get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, Integer.class); - if (messageSequenceSize == null) { - messageSequenceSize = 0; - } - return messageSequenceSize.equals(getSequenceSize()) - && !(this.sourceGroup != null ? this.sourceGroup.containsSequence(messageSequenceNumber) - : containsSequenceNumber(this.getMessages(), messageSequenceNumber)); - } - return true; - } - - private boolean containsSequenceNumber(Collection> messages, Integer messageSequenceNumber) { - for (Message member : messages) { - if (messageSequenceNumber.equals(member.getHeaders().get( - IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, Integer.class))) { - return true; - } - } - return false; - } - - } - - private class ForceReleaseMessageGroupProcessor implements MessageGroupProcessor { - - ForceReleaseMessageGroupProcessor() { - } - - @Override - @Nullable - public Object processMessageGroup(MessageGroup group) { - forceComplete(group); - return null; - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AggregatingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AggregatingMessageHandler.java deleted file mode 100644 index b2c2d84f3f1..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AggregatingMessageHandler.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Collection; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.store.SimpleMessageStore; -import org.springframework.messaging.Message; - -/** - * Aggregator specific implementation of {@link AbstractCorrelatingMessageHandler}. - * Will remove {@link MessageGroup}s in the {@linkplain #afterRelease} - * only if 'expireGroupsUponCompletion' flag is set to 'true'. - * - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * - * @since 2.1 - */ -public class AggregatingMessageHandler extends AbstractCorrelatingMessageHandler { - - private volatile boolean expireGroupsUponCompletion = false; - - public AggregatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store, - @Nullable CorrelationStrategy correlationStrategy, @Nullable ReleaseStrategy releaseStrategy) { - - super(processor, store, correlationStrategy, releaseStrategy); - } - - public AggregatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) { - super(processor, store); - } - - public AggregatingMessageHandler(MessageGroupProcessor processor) { - super(processor); - } - - /** - * Will set the 'expireGroupsUponCompletion' flag. - * @param expireGroupsUponCompletion true when groups should be expired on completion. - * @see #afterRelease - */ - public void setExpireGroupsUponCompletion(boolean expireGroupsUponCompletion) { - this.expireGroupsUponCompletion = expireGroupsUponCompletion; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.aggregator; - } - - @Override - protected boolean isExpireGroupsUponCompletion() { - return this.expireGroupsUponCompletion; - } - - /** - * Check an {@link Iterable} result for split possibility on the output production: - * the items of the collection have to be instances of {@link Message} - * or {@link org.springframework.integration.support.AbstractIntegrationMessageBuilder} - * and {@link #getOutputProcessor()} has to be a {@link SimpleMessageGroupProcessor}. - * Otherwise, a single reply message is emitted with the whole {@link Iterable} as its payload. - * @param reply the {@link Iterable} result to check for split possibility. - * @return true if the {@link Iterable} result has to be split into individual messages. - * @since 6.0 - */ - @Override - protected boolean shouldSplitOutput(Iterable reply) { - return getOutputProcessor() instanceof SimpleMessageGroupProcessor && super.shouldSplitOutput(reply); - } - - /** - * Complete the group and remove all its messages. - * If the {@link #expireGroupsUponCompletion} is true, then remove group fully. - * @param messageGroup the group to clean up. - * @param completedMessages The completed messages. Ignored in this implementation. - */ - @Override - protected void afterRelease(MessageGroup messageGroup, @Nullable Collection> completedMessages) { - Object groupId = messageGroup.getGroupId(); - MessageGroupStore messageStore = getMessageStore(); - messageStore.completeGroup(groupId); - - if (this.expireGroupsUponCompletion) { - remove(messageGroup); - } - else { - if (messageStore instanceof SimpleMessageStore simpleMessageStore) { - simpleMessageStore.clearMessageGroup(groupId); - } - else { - messageStore.removeMessagesFromGroup(groupId, messageGroup.getMessages()); - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/BarrierMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/BarrierMessageHandler.java deleted file mode 100644 index 1d9082f4b38..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/BarrierMessageHandler.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.DiscardingMessageHandler; -import org.springframework.integration.handler.MessageTriggerAction; -import org.springframework.integration.store.SimpleMessageGroup; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.MessagingException; -import org.springframework.util.Assert; - -/** - * A message handler that suspends the thread until a message with corresponding - * correlation is passed into the {@link #trigger(Message) trigger} method or - * the timeout occurs. Only one thread with a particular correlation (result of invoking - * the {@link CorrelationStrategy}) can be suspended at a time. If the inbound thread does - * not arrive before the trigger thread, the latter is suspended until it does, or the - * timeout occurs. Separate timeouts may be configured for request and trigger messages. - *

- * The default {@link CorrelationStrategy} is a {@link HeaderAttributeCorrelationStrategy}. - *

- * The default output processor is a {@link DefaultAggregatingMessageGroupProcessor}. - * - * @author Gary Russell - * @author Artem Bilan - * @author Michel Jung - * - * @since 4.2 - */ -public class BarrierMessageHandler extends AbstractReplyProducingMessageHandler - implements MessageTriggerAction, DiscardingMessageHandler { - - private final Map>> suspensions = new ConcurrentHashMap<>(); - - private final Map inProcess = new ConcurrentHashMap<>(); - - private final long requestTimeout; - - private final long triggerTimeout; - - private final CorrelationStrategy correlationStrategy; - - private final MessageGroupProcessor messageGroupProcessor; - - @Nullable - private String discardChannelName; - - @Nullable - private MessageChannel discardChannel; - - /** - * Construct an instance with the provided timeout and default correlation and - * output strategies. - * @param timeout the timeout in milliseconds for both, request and trigger messages. - */ - public BarrierMessageHandler(long timeout) { - this(timeout, timeout); - } - - /** - * Construct an instance with the provided timeout and output processor, and default - * correlation strategy. - * @param timeout the timeout in milliseconds for both, request and trigger messages. - * @param outputProcessor the output {@link MessageGroupProcessor}. - */ - public BarrierMessageHandler(long timeout, MessageGroupProcessor outputProcessor) { - this(timeout, timeout, outputProcessor); - } - - /** - * Construct an instance with the provided timeout and correlation strategy, and default - * output processor. - * @param timeout the timeout in milliseconds for both, request and trigger messages. - * @param correlationStrategy the correlation strategy. - */ - public BarrierMessageHandler(long timeout, CorrelationStrategy correlationStrategy) { - this(timeout, timeout, correlationStrategy); - } - - /** - * Construct an instance with the provided timeout and output processor, and default - * correlation strategy. - * @param timeout the timeout in milliseconds for both, request and trigger messages. - * @param outputProcessor the output {@link MessageGroupProcessor}. - * @param correlationStrategy the correlation strategy. - */ - public BarrierMessageHandler(long timeout, MessageGroupProcessor outputProcessor, - CorrelationStrategy correlationStrategy) { - - this(timeout, timeout, outputProcessor, correlationStrategy); - } - - /** - * Construct an instance with the provided timeouts and default correlation and - * output strategies. - * @param requestTimeout the timeout in milliseconds when waiting for trigger message. - * @param triggerTimeout the timeout in milliseconds when waiting for a request message. - * @since 5.4 - */ - public BarrierMessageHandler(long requestTimeout, long triggerTimeout) { - this(requestTimeout, triggerTimeout, new DefaultAggregatingMessageGroupProcessor()); - } - - /** - * Construct an instance with the provided timeout and output processor, and default - * correlation strategy. - * @param requestTimeout the timeout in milliseconds when waiting for trigger message. - * @param triggerTimeout the timeout in milliseconds when waiting for a request message. - * @param outputProcessor the output {@link MessageGroupProcessor}. - * @since 5.4 - */ - public BarrierMessageHandler(long requestTimeout, long triggerTimeout, MessageGroupProcessor outputProcessor) { - this(requestTimeout, triggerTimeout, outputProcessor, null); - } - - /** - * Construct an instance with the provided timeout and correlation strategy, and default - * output processor. - * @param requestTimeout the timeout in milliseconds when waiting for trigger message. - * @param triggerTimeout the timeout in milliseconds when waiting for a request message. - * @param correlationStrategy the correlation strategy. - * @since 5.4 - */ - public BarrierMessageHandler(long requestTimeout, long triggerTimeout, CorrelationStrategy correlationStrategy) { - this(requestTimeout, triggerTimeout, new DefaultAggregatingMessageGroupProcessor(), correlationStrategy); - } - - /** - * Construct an instance with the provided timeout and output processor, and default - * correlation strategy. - * @param requestTimeout the timeout in milliseconds when waiting for trigger message. - * @param triggerTimeout the timeout in milliseconds when waiting for a request message. - * @param outputProcessor the output {@link MessageGroupProcessor}. - * @param correlationStrategy the correlation strategy. - * @since 5.4 - */ - public BarrierMessageHandler(long requestTimeout, long triggerTimeout, MessageGroupProcessor outputProcessor, - @Nullable CorrelationStrategy correlationStrategy) { - - Assert.notNull(outputProcessor, "'messageGroupProcessor' cannot be null"); - this.messageGroupProcessor = outputProcessor; - this.correlationStrategy = - correlationStrategy == null - ? new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID) - : correlationStrategy; - this.requestTimeout = requestTimeout; - this.triggerTimeout = triggerTimeout; - } - - /** - * Set the name of the channel to which late arriving trigger messages are sent, - * or request message does not arrive in time. - * @param discardChannelName the discard channel. - * @since 5.0 - */ - public void setDiscardChannelName(String discardChannelName) { - this.discardChannelName = discardChannelName; - } - - /** - * Set the channel to which late arriving trigger messages are sent, - * or request message does not arrive in time. - * @param discardChannel the discard channel. - * @since 5.0 - */ - public void setDiscardChannel(MessageChannel discardChannel) { - this.discardChannel = discardChannel; - } - - /** - * Return the discard message channel for trigger action message. - * @return a discard message channel. - * @since 5.0 - */ - @Nullable - @Override - public MessageChannel getDiscardChannel() { - String channelName = this.discardChannelName; - if (channelName != null) { - this.discardChannel = getChannelResolver().resolveDestination(channelName); - this.discardChannelName = null; - } - return this.discardChannel; - } - - @Override - public String getComponentType() { - return "barrier"; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.barrier; - } - - @Override - @Nullable - protected Object handleRequestMessage(Message requestMessage) { - Object key = this.correlationStrategy.getCorrelationKey(requestMessage); - if (key == null) { - throw new MessagingException(requestMessage, "Correlation Strategy returned null"); - } - Thread existing = this.inProcess.putIfAbsent(key, Thread.currentThread()); - if (existing != null) { - throw new MessagingException(requestMessage, - "Correlation key (" + key + ") is already in use by " + existing.getName()); - } - SynchronousQueue> syncQueue = createOrObtainQueue(key); - try { - Message releaseMessage = syncQueue.poll(this.requestTimeout, TimeUnit.MILLISECONDS); - if (releaseMessage != null) { - return processRelease(key, requestMessage, releaseMessage); - } - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new MessageHandlingException(requestMessage, - "Interrupted while waiting for release in the [" + this + ']', e); - } - finally { - this.inProcess.remove(key); - this.suspensions.remove(key); - } - return null; - } - - @Nullable - private Object processRelease(Object key, Message requestMessage, Message releaseMessage) { - this.suspensions.remove(key); - if (releaseMessage.getPayload() instanceof Throwable) { - throw new MessagingException(requestMessage, "Releasing flow returned a throwable", - (Throwable) releaseMessage.getPayload()); - } - else { - return buildResult(key, requestMessage, releaseMessage); - } - } - - /** - * Override to change the default mechanism by which the inbound and release messages - * are returned as a result. - * @param key The correlation key. - * @param requestMessage the inbound message. - * @param releaseMessage the release message. - * @return the result. - */ - @Nullable - protected Object buildResult(Object key, Message requestMessage, Message releaseMessage) { - SimpleMessageGroup group = new SimpleMessageGroup(key); - group.add(requestMessage); - group.add(releaseMessage); - return this.messageGroupProcessor.processMessageGroup(group); - } - - private SynchronousQueue> createOrObtainQueue(Object key) { - SynchronousQueue> syncQueue = new SynchronousQueue<>(); - SynchronousQueue> existing = this.suspensions.putIfAbsent(key, syncQueue); - if (existing != null) { - syncQueue = existing; - } - return syncQueue; - } - - @Override - public void trigger(Message message) { - Object key = this.correlationStrategy.getCorrelationKey(message); - if (key == null) { - throw new MessagingException(message, "Correlation Strategy returned null"); - } - SynchronousQueue> syncQueue = createOrObtainQueue(key); - try { - if (!syncQueue.offer(message, this.triggerTimeout, TimeUnit.MILLISECONDS)) { - this.logger.error("Suspending thread timed out or did not arrive within timeout for: " + message); - this.suspensions.remove(key); - MessageChannel messageChannel = getDiscardChannel(); - if (messageChannel != null) { - this.messagingTemplate.send(messageChannel, message); - } - } - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - this.logger.error("Interrupted while waiting for the suspending thread for: " + message); - this.suspensions.remove(key); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageBarrier.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageBarrier.java deleted file mode 100644 index 7babbacf296..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageBarrier.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogMessage; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.store.SimpleMessageStore; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * This Endpoint serves as a barrier for messages that should not be processed yet. - * The decision when a message can be processed is delegated to a {@link ReleaseStrategy}. - * When a message can be processed it is up to the client to take care of the locking - * (potentially from the {@link ReleaseStrategy#canRelease(MessageGroup)} method). - *

- * This class differs from AbstractCorrelatingMessageHandler in that it completely decouples - * the receiver and the sender. - * It can be applied in scenarios where completion of a message group is not well-defined - * but only a certain amount of messages for any given correlation key may be processed at a time. - *

- * The messages will be stored in a {@link MessageGroupStore} for each correlation key. - * - * @author Iwein Fuld - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Trung Pham - * @author Glenn Renfro - * - * @see AbstractCorrelatingMessageHandler - */ -public class CorrelatingMessageBarrier extends AbstractMessageHandler implements MessageSource { - - private final ConcurrentMap correlationLocks = new ConcurrentHashMap<>(); - - private final MessageGroupStore store; - - @SuppressWarnings("NullAway.Init") - private CorrelationStrategy correlationStrategy; - - @SuppressWarnings("NullAway.Init") - private ReleaseStrategy releaseStrategy; - - public CorrelatingMessageBarrier() { - this(new SimpleMessageStore(0)); - } - - public CorrelatingMessageBarrier(MessageGroupStore store) { - this.store = store; - } - - /** - * Set the CorrelationStrategy to be used to determine the correlation key for incoming messages. - * @param correlationStrategy The correlation strategy. - */ - public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) { - this.correlationStrategy = correlationStrategy; - } - - /** - * Set the ReleaseStrategy that should be used when deciding if a group in this barrier may be released. - * @param releaseStrategy The release strategy. - */ - public void setReleaseStrategy(ReleaseStrategy releaseStrategy) { - this.releaseStrategy = releaseStrategy; - } - - @Override - protected void onInit() { - super.onInit(); - Assert.notNull(this.releaseStrategy, "'releaseStrategy' must not be null"); - Assert.notNull(this.correlationStrategy, "'correlationStrategy' must not be null"); - } - - @Override - protected void handleMessageInternal(Message message) { - Object correlationKey = this.correlationStrategy.getCorrelationKey(message); - Assert.notNull(correlationKey, "The correlation key is required"); - Object lock = getLock(correlationKey); - synchronized (lock) { - this.store.addMessagesToGroup(correlationKey, message); - } - logger.debug(LogMessage.format("Handled message for key [%s]: %s.", correlationKey, message)); - } - - private Object getLock(Object correlationKey) { - Object existingLock = this.correlationLocks.putIfAbsent(correlationKey, correlationKey); - return existingLock == null ? correlationKey : existingLock; - } - - @SuppressWarnings("unchecked") - @Override - public @Nullable Message receive() { - for (Object key : this.correlationLocks.keySet()) { - Object lock = getLock(key); - synchronized (lock) { - MessageGroup group = this.store.getMessageGroup(key); - //group might be removed by another thread - if (group != null && this.releaseStrategy.canRelease(group)) { - Message nextMessage = null; - Iterator> messages = group.getMessages().iterator(); - if (messages.hasNext()) { - nextMessage = messages.next(); - this.store.removeMessagesFromGroup(key, nextMessage); - logger.debug(LogMessage.format("Released message for key [%s]: %s.", key, nextMessage)); - } - else { - remove(key); - } - return (Message) nextMessage; - } - } - } - return null; - } - - private void remove(Object key) { - this.correlationLocks.remove(key); - this.store.removeMessageGroup(key); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelationStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelationStrategy.java deleted file mode 100644 index 5e8d211031e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelationStrategy.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.jspecify.annotations.Nullable; - -import org.springframework.messaging.Message; - -/** - * Strategy for determining how messages can be correlated. Implementations - * should return the correlation key value associated with a particular message. - * - * @author Marius Bogoevici - * @author Iwein Fuld - */ -@FunctionalInterface -public interface CorrelationStrategy { - - /** - * Find the correlation key for the given message. If no key can be determined the strategy should not return - * null, but throw an exception. - * - * @param message The message. - * @return The correlation key. - */ - @Nullable - Object getCorrelationKey(Message message); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregateHeadersFunction.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregateHeadersFunction.java deleted file mode 100644 index 8bdc39a8760..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregateHeadersFunction.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.store.MessageGroup; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; - -/** - * The {@link Function} implementation for a default headers merging in the aggregator - * component. It takes all the unique headers from all the messages in group and removes - * those which are conflicted: have different values from different messages. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see AbstractAggregatingMessageGroupProcessor - */ -public class DefaultAggregateHeadersFunction implements Function> { - - private static final Log LOGGER = LogFactory.getLog(DefaultAggregateHeadersFunction.class); - - @Override - public Map apply(MessageGroup messageGroup) { - Map aggregatedHeaders = new HashMap<>(); - Set conflictKeys = doAggregateHeaders(messageGroup, aggregatedHeaders); - for (String keyToRemove : conflictKeys) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Excluding header '" + keyToRemove + "' upon aggregation due to conflict(s) " - + "in MessageGroup with correlation key: " + messageGroup.getGroupId()); - } - aggregatedHeaders.remove(keyToRemove); - } - return aggregatedHeaders; - } - - private Set doAggregateHeaders(MessageGroup group, Map aggregatedHeaders) { - Set conflictKeys = new HashSet<>(); - for (Message message : group.getMessages()) { - for (Map.Entry entry : message.getHeaders().entrySet()) { - String key = entry.getKey(); - if (MessageHeaders.ID.equals(key) - || MessageHeaders.TIMESTAMP.equals(key) - || IntegrationMessageHeaderAccessor.SEQUENCE_SIZE.equals(key) - || IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER.equals(key)) { - continue; - } - Object value = entry.getValue(); - if (!aggregatedHeaders.containsKey(key)) { - aggregatedHeaders.put(key, value); - } - else { - if (!Objects.equals(value, aggregatedHeaders.get(key))) { - conflictKeys.add(key); - } - } - } - } - return conflictKeys; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java deleted file mode 100644 index 3f8d32f5b02..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DefaultAggregatingMessageGroupProcessor.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.springframework.integration.store.MessageGroup; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * This implementation of MessageGroupProcessor will take the messages from the - * MessageGroup and pass them on in a single message with a Collection as a payload. - * - * @author Iwein Fuld - * @author Alexander Peters - * @author Mark Fisher - * @since 2.0 - */ -public class DefaultAggregatingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor { - - @Override - protected final Object aggregatePayloads(MessageGroup group, Map headers) { - Collection> messages = group.getMessages(); - Assert.notEmpty(messages, this.getClass().getSimpleName() + " cannot process empty message groups"); - List payloads = new ArrayList(messages.size()); - for (Message message : messages) { - payloads.add(message.getPayload()); - } - return payloads; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DelegatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DelegatingMessageGroupProcessor.java deleted file mode 100644 index 642a0b06183..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/DelegatingMessageGroupProcessor.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Map; -import java.util.function.Function; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.context.Lifecycle; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.DefaultMessageBuilderFactory; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * The {@link MessageGroupProcessor} implementation with delegation to the provided {@code delegate} - * and optional aggregation for headers. - *

- * Unlike {@link AbstractAggregatingMessageGroupProcessor} this processor checks a result - * of the {@code delegate} call and aggregates headers into the output only - * if the result is not a {@link Message} or {@link AbstractIntegrationMessageBuilder}. - *

- * This processor is used internally for wrapping provided non-standard {@link MessageGroupProcessor} - * when a aggregate headers {@link Function} is provided. - * For POJO method invoking or SpEL expression evaluation it is recommended to use an - * {@link AbstractAggregatingMessageGroupProcessor} implementations. - * - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class DelegatingMessageGroupProcessor implements MessageGroupProcessor, BeanFactoryAware, - ManageableLifecycle { - - private final MessageGroupProcessor delegate; - - private final Function> headersFunction; - - private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - private volatile boolean messageBuilderFactorySet; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - public DelegatingMessageGroupProcessor(MessageGroupProcessor delegate, - Function> headersFunction) { - - Assert.notNull(delegate, "'delegate' must not be null"); - Assert.notNull(headersFunction, "'headersFunction' must not be null"); - this.delegate = delegate; - this.headersFunction = headersFunction; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - if (this.delegate instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.delegate).setBeanFactory(beanFactory); - } - } - - @Override - public Object processMessageGroup(MessageGroup group) { - Object result = this.delegate.processMessageGroup(group); - if (!(result instanceof Message) && !(result instanceof AbstractIntegrationMessageBuilder)) { - result = getMessageBuilderFactory() - .withPayload(result) - .copyHeadersIfAbsent(this.headersFunction.apply(group)); - } - return result; - } - - private MessageBuilderFactory getMessageBuilderFactory() { - if (!this.messageBuilderFactorySet) { - if (this.beanFactory != null) { - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - } - this.messageBuilderFactorySet = true; - } - return this.messageBuilderFactory; - } - - @Override - public void start() { - if (this.delegate instanceof Lifecycle) { - ((Lifecycle) this.delegate).start(); - } - } - - @Override - public void stop() { - if (this.delegate instanceof Lifecycle) { - ((Lifecycle) this.delegate).stop(); - } - } - - @Override - public boolean isRunning() { - return this.delegate instanceof Lifecycle && ((Lifecycle) this.delegate).isRunning(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java deleted file mode 100644 index 8c6a1ace9bd..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.SpelParserConfiguration; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * {@link CorrelationStrategy} implementation that evaluates an expression. - * - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Artem Bilan - */ -public class ExpressionEvaluatingCorrelationStrategy implements CorrelationStrategy, BeanFactoryAware { - - private static final ExpressionParser EXPRESSION_PARSER = - new SpelExpressionParser(new SpelParserConfiguration(true, true)); - - private final ExpressionEvaluatingMessageProcessor processor; - - public ExpressionEvaluatingCorrelationStrategy(String expressionString) { - Assert.hasText(expressionString, "expressionString must not be empty"); - Expression expression = EXPRESSION_PARSER.parseExpression(expressionString); - this.processor = new ExpressionEvaluatingMessageProcessor<>(expression, Object.class); - } - - public ExpressionEvaluatingCorrelationStrategy(Expression expression) { - this.processor = new ExpressionEvaluatingMessageProcessor<>(expression, Object.class); - } - - public @Nullable Object getCorrelationKey(Message message) { - return this.processor.processMessage(message); - } - - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.processor.setBeanFactory(beanFactory); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java deleted file mode 100644 index 93c0653fa81..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Map; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.convert.ConversionService; -import org.springframework.integration.store.MessageGroup; -import org.springframework.util.Assert; - -/** - * A {@link MessageGroupProcessor} implementation that evaluates a SpEL expression. The SpEL context root is the list of - * all Messages in the group. The evaluation result can be any Object and is send as new Message payload to the output - * channel. - * - * @author Alex Peters - * @author Dave Syer - * @author Gary Russell - */ -public class ExpressionEvaluatingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor { - - private final ExpressionEvaluatingMessageListProcessor processor; - - public ExpressionEvaluatingMessageGroupProcessor(String expression) { - this.processor = new ExpressionEvaluatingMessageListProcessor(expression); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - super.setBeanFactory(beanFactory); - this.processor.setBeanFactory(beanFactory); - } - - public void setConversionService(ConversionService conversionService) { - this.processor.setConversionService(conversionService); - } - - public void setExpectedType(Class expectedType) { - this.processor.setExpectedType(expectedType); - } - - /** - * Evaluate the expression provided on the messages (a collection) in the group, and delegate to the - * {@link org.springframework.integration.core.MessagingTemplate} to send downstream. - */ - @Override - protected Object aggregatePayloads(MessageGroup group, Map headers) { - Object object = this.processor.process(group.getMessages()); - Assert.state(object != null, "The process returned a null result. Null result is not expected."); - return object; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java deleted file mode 100644 index bdfc86bf049..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageListProcessor.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Collection; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.expression.ParseException; -import org.springframework.integration.util.AbstractExpressionEvaluator; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * A base class for aggregators that evaluates a SpEL expression with the message list as the root object within the - * evaluation context. - * - * @author Dave Syer - * @author Artem Bilan - * @since 2.0 - */ -public class ExpressionEvaluatingMessageListProcessor extends AbstractExpressionEvaluator - implements MessageListProcessor { - - private final Expression expression; - - @Nullable - private volatile Class expectedType = null; - - /** - * Construct {@link ExpressionEvaluatingMessageListProcessor} for the provided - * SpEL expression and expected result type. - * @param expression a SpEL expression to evaluate in {@link #process(Collection)}. - * @param expectedType an expected result type. - * @since 5.0 - */ - public ExpressionEvaluatingMessageListProcessor(String expression, Class expectedType) { - this(expression); - this.expectedType = expectedType; - } - - /** - * Construct {@link ExpressionEvaluatingMessageListProcessor} for the provided - * SpEL expression and expected result type. - * @param expression a SpEL expression to evaluate in {@link #process(Collection)}. - * @since 5.0 - */ - public ExpressionEvaluatingMessageListProcessor(String expression) { - try { - this.expression = EXPRESSION_PARSER.parseExpression(expression); - } - catch (ParseException e) { - throw new IllegalArgumentException("Failed to parse expression.", e); - } - } - - /** - * Construct {@link ExpressionEvaluatingMessageListProcessor} for the provided - * expression and expected result type. - * @param expression an expression to evaluate in {@link #process(Collection)}. - * @param expectedType an expected result type. - * @since 5.0 - */ - public ExpressionEvaluatingMessageListProcessor(Expression expression, Class expectedType) { - this(expression); - this.expectedType = expectedType; - } - - /** - * Construct {@link ExpressionEvaluatingMessageListProcessor} for the provided expression. - * @param expression an expression to evaluate in {@link #process(Collection)}. - * @since 5.0 - */ - public ExpressionEvaluatingMessageListProcessor(Expression expression) { - Assert.notNull(expression, "'expression' must not be null."); - this.expression = expression; - } - - /** - * Set the result type expected from evaluation of the expression. - * @param expectedType The expected type. - */ - public void setExpectedType(Class expectedType) { - this.expectedType = expectedType; - } - - /** - * Processes the Message by evaluating the expression with that Message as the root object. The expression - * evaluation result Object will be returned. - */ - @Override - public Object process(Collection> messages) { - Object object = this.evaluateExpression(this.expression, messages, this.expectedType); - Assert.state(object != null, "The evaluation of the expression returned a null. Null result is not expected: " + this.expression); - return object; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingReleaseStrategy.java deleted file mode 100644 index c4e96ca86bb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingReleaseStrategy.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.springframework.expression.Expression; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.util.AbstractExpressionEvaluator; -import org.springframework.util.Assert; - -/** - * A {@link ReleaseStrategy} that evaluates an expression. - * - * @author Dave Syer - * @author Artem Bilan - */ -public class ExpressionEvaluatingReleaseStrategy extends AbstractExpressionEvaluator implements ReleaseStrategy { - - private final Expression expression; - - public ExpressionEvaluatingReleaseStrategy(String expression) { - Assert.hasText(expression, "'expression' must not be empty"); - this.expression = EXPRESSION_PARSER.parseExpression(expression); - } - - public ExpressionEvaluatingReleaseStrategy(Expression expression) { - Assert.notNull(expression, "'expression' must not be null"); - this.expression = expression; - } - - /** - * Evaluate the expression provided on the {@link MessageGroup} - * and return the result (must be boolean). - */ - public boolean canRelease(MessageGroup messages) { - return Boolean.TRUE.equals(evaluateExpression(this.expression, messages, Boolean.class)); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/FluxAggregatorMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/FluxAggregatorMessageHandler.java deleted file mode 100644 index af5136d3b69..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/FluxAggregatorMessageHandler.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.time.Duration; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.jspecify.annotations.Nullable; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.FluxSink; -import reactor.core.publisher.Mono; - -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.util.Assert; - -/** - * The {@link AbstractMessageProducingHandler} implementation for aggregation logic based - * on Reactor's {@link Flux#groupBy} and {@link Flux#window} operators. - *

- * The incoming messages are emitted into a {@link FluxSink} provided by the - * {@link Flux#create} initialized in the constructor. - *

- * The resulting windows for groups are wrapped into {@link Message}s for downstream - * consumption. - *

- * If the {@link #getOutputChannel()} is not a {@link ReactiveStreamsSubscribableChannel} - * instance, a subscription for the whole aggregating {@link Flux} is performed in the - * {@link #start()} method. - * - * @author Artem Bilan - * @author Glenn Renfro - * - * @since 5.2 - */ -public class FluxAggregatorMessageHandler extends AbstractMessageProducingHandler implements ManageableLifecycle { - - private final AtomicBoolean subscribed = new AtomicBoolean(); - - private final Flux> aggregatorFlux; - - private CorrelationStrategy correlationStrategy = - new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID); - - @Nullable - private Predicate> boundaryTrigger; - - private Function, @Nullable Integer> windowSizeFunction = FluxAggregatorMessageHandler::sequenceSizeHeader; - - @Nullable - private Function>, Flux>>> windowConfigurer; - - @Nullable - private Duration windowTimespan; - - private Function>, Mono>> combineFunction = this::messageForWindowFlux; - - @SuppressWarnings("NullAway.Init") - private FluxSink> sink; - - @Nullable - private volatile Disposable subscription; - - /** - * Create an instance with a {@link Flux#create} and apply {@link Flux#groupBy} and {@link Flux#window} - * transformation into it. - */ - public FluxAggregatorMessageHandler() { - this.aggregatorFlux = - Flux.>create(emitter -> this.sink = emitter, FluxSink.OverflowStrategy.BUFFER) - .groupBy(this::groupBy) - .flatMap((group) -> group.transform(this::releaseBy)) - .publish() - .autoConnect(); - } - - private Object groupBy(Message message) { - Object result = this.correlationStrategy.getCorrelationKey(message); - Assert.notNull(result, "Correlation key cannot be null"); - return result; - } - - private Flux> releaseBy(Flux> groupFlux) { - return groupFlux - .transform(this.windowConfigurer != null ? this.windowConfigurer : this::applyWindowOptions) - .flatMap((windowFlux) -> windowFlux.transform(this.combineFunction)); - } - - private Flux>> applyWindowOptions(Flux> groupFlux) { - if (this.boundaryTrigger != null) { - return groupFlux.windowUntil(this.boundaryTrigger); - } - return groupFlux - .switchOnFirst((signal, group) -> { - if (signal.hasValue()) { - Integer maxSize = this.windowSizeFunction.apply(Objects.requireNonNull(signal.get())); - if (maxSize != null) { - if (this.windowTimespan != null) { - return group.windowTimeout(maxSize, this.windowTimespan); - } - else { - return group.window(maxSize); - } - } - else { - if (this.windowTimespan != null) { - return group.window(this.windowTimespan); - } - else { - return Flux.error( - new IllegalStateException( - "One of the 'boundaryTrigger', 'windowSizeFunction' or " - + "'windowTimespan' options must be configured or " + - "'sequenceSize' header must be supplied in the messages " + - "to aggregate.")); - } - } - } - else { - return Flux.just(group); - } - }); - } - - /** - * Configure a {@link CorrelationStrategy} to determine a group key from the incoming messages. - * By default, a {@link HeaderAttributeCorrelationStrategy} is used against a - * {@link IntegrationMessageHeaderAccessor#CORRELATION_ID} header value. - * @param correlationStrategy the {@link CorrelationStrategy} to use. - */ - public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) { - Assert.notNull(correlationStrategy, "'correlationStrategy' must not be null"); - this.correlationStrategy = correlationStrategy; - } - - /** - * Configure a transformation {@link Function} to apply for a {@link Flux} window to emit. - * Requires a {@link Mono} result with a {@link Message} as value as a combination result - * of the incoming {@link Flux} for window. - * By default, a {@link Flux} for window is fully wrapped into a message with headers copied - * from the first message in window. Such a {@link Flux} in the payload has to be subscribed - * and consumed downstream. - * @param combineFunction the {@link Function} to use for result windows transformation. - */ - public void setCombineFunction(Function>, Mono>> combineFunction) { - Assert.notNull(combineFunction, "'combineFunction' must not be null"); - this.combineFunction = combineFunction; - } - - /** - * Configure a {@link Predicate} for messages to determine a window boundary in the - * {@link Flux#windowUntil} operator. - * Has a precedence over any other window configuration options. - * @param boundaryTrigger the {@link Predicate} to use for window boundary. - * @see Flux#windowUntil(Predicate) - */ - public void setBoundaryTrigger(Predicate> boundaryTrigger) { - this.boundaryTrigger = boundaryTrigger; - } - - /** - * Specify a size for windows to close. - * Can be combined with the {@link #setWindowTimespan(Duration)}. - * @param windowSize the size for window to use. - * @see Flux#window(int) - * @see Flux#windowTimeout(int, Duration) - */ - public void setWindowSize(int windowSize) { - setWindowSizeFunction((message) -> windowSize); - } - - /** - * Specify a {@link Function} to determine a size for windows to close against the first message in group. - * Tne result of the function can be combined with the {@link #setWindowTimespan(Duration)}. - * By default, an {@link IntegrationMessageHeaderAccessor#SEQUENCE_SIZE} header is consulted. - * @param windowSizeFunction the {@link Function} to use to determine a window size - * against a first message in the group. - * @see Flux#window(int) - * @see Flux#windowTimeout(int, Duration) - */ - public void setWindowSizeFunction(Function, @Nullable Integer> windowSizeFunction) { - Assert.notNull(windowSizeFunction, "'windowSizeFunction' must not be null"); - this.windowSizeFunction = windowSizeFunction; - } - - /** - * Configure a {@link Duration} for closing windows periodically. - * Can be combined with the {@link #setWindowSize(int)} or {@link #setWindowSizeFunction(Function)}. - * @param windowTimespan the {@link Duration} to use for windows to close periodically. - * @see Flux#window(Duration) - * @see Flux#windowTimeout(int, Duration) - */ - public void setWindowTimespan(Duration windowTimespan) { - this.windowTimespan = windowTimespan; - } - - /** - * Configure a {@link Function} to apply a transformation into the grouping {@link Flux} - * for any arbitrary {@link Flux#window} options not covered by the simple options. - * Has a precedence over any other window configuration options. - * @param windowConfigurer the {@link Function} to apply any custom window transformation. - */ - public void setWindowConfigurer(Function>, Flux>>> windowConfigurer) { - this.windowConfigurer = windowConfigurer; - } - - @Override - public String getComponentType() { - return "flux-aggregator"; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.aggregator; - } - - @Override - public void start() { - if (this.subscribed.compareAndSet(false, true)) { - MessageChannel outputChannel = getOutputChannel(); - if (outputChannel instanceof ReactiveStreamsSubscribableChannel) { - ((ReactiveStreamsSubscribableChannel) outputChannel).subscribeTo(this.aggregatorFlux); - } - else { - this.subscription = - this.aggregatorFlux.subscribe((messageToSend) -> produceOutput(messageToSend, messageToSend)); - } - } - } - - @Override - public void stop() { - Disposable subscriptionToDispose = this.subscription; - if (this.subscribed.compareAndSet(true, false) && subscriptionToDispose != null) { - subscriptionToDispose.dispose(); - } - } - - @Override - public boolean isRunning() { - return this.subscribed.get(); - } - - @Override - protected void handleMessageInternal(Message message) { - Assert.state(isRunning(), - "The 'FluxAggregatorMessageHandler' has not been started to accept incoming messages"); - - this.sink.next(message); - } - - @Override - protected boolean shouldCopyRequestHeaders() { - return false; - } - - private Mono> messageForWindowFlux(Flux> messageFlux) { - Flux> window = messageFlux.publish().autoConnect(); - return window - .next() - .map((first) -> - getMessageBuilderFactory() - .withPayload(Flux.concat(Mono.just(first), window)) - .copyHeaders(first.getHeaders()) - .build()); - } - - private static @Nullable Integer sequenceSizeHeader(Message message) { - return message.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, Integer.class); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/GroupConditionProvider.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/GroupConditionProvider.java deleted file mode 100644 index a512da4d648..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/GroupConditionProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.function.BiFunction; - -import org.springframework.messaging.Message; - -/** - * A contract which can be implemented on the {@link ReleaseStrategy} - * and used in the {@link AbstractCorrelatingMessageHandler} to - * populate the provided group condition supplier. - * - * @author Artem Bilan - * - * @since 5.5 - * - * @see AbstractCorrelatingMessageHandler#setGroupConditionSupplier(BiFunction) - */ -@FunctionalInterface -public interface GroupConditionProvider { - - BiFunction, String, String> getGroupConditionSupplier(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/HeaderAttributeCorrelationStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/HeaderAttributeCorrelationStrategy.java deleted file mode 100644 index aa0e272845d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/HeaderAttributeCorrelationStrategy.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.jspecify.annotations.Nullable; - -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Default implementation of {@link CorrelationStrategy}. - * Uses a provided header attribute to determine the correlation key value. - * - * @author Marius Bogoevici - * @author Artem Bilan - */ -public class HeaderAttributeCorrelationStrategy implements CorrelationStrategy { - - private final String attributeName; - - public HeaderAttributeCorrelationStrategy(String attributeName) { - Assert.hasText(attributeName, "the 'attributeName' must not be empty"); - this.attributeName = attributeName; - } - - @Nullable - public Object getCorrelationKey(Message message) { - return message.getHeaders().get(this.attributeName); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageCountReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageCountReleaseStrategy.java deleted file mode 100755 index e99f7afd876..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageCountReleaseStrategy.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.springframework.integration.store.MessageGroup; - -/** - * A {@link ReleaseStrategy} that releases only the first {@code n} messages, where {@code n} is a threshold. - * - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Artem Bilan - * - */ -public class MessageCountReleaseStrategy implements ReleaseStrategy { - - private final int threshold; - - /** - * Convenient constructor is only one message is required (threshold=1). - */ - public MessageCountReleaseStrategy() { - this(1); - } - - /** - * Construct an instance based on the provided threshold. - * @param threshold the number of messages to accept before releasing - */ - public MessageCountReleaseStrategy(int threshold) { - this.threshold = threshold; - } - - /** - * Release the group if it has more messages than the threshold and has not previously been released. - * It is possible that more messages than the threshold could be released, but only if multiple consumers - * receive messages from the same group concurrently. - */ - @Override - public boolean canRelease(MessageGroup group) { - return group.size() >= this.threshold; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupExpiredEvent.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupExpiredEvent.java deleted file mode 100644 index 7e036147857..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupExpiredEvent.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Date; - -import org.springframework.integration.events.IntegrationEvent; -import org.springframework.integration.support.context.NamedComponent; - -/** - * Event representing the expiration of a message group. - * - * @author Gary Russell - * - * @since 4.0.1 - */ -public class MessageGroupExpiredEvent extends IntegrationEvent { - - private static final long serialVersionUID = -7126221042599333919L; - - @SuppressWarnings("serial") - private final Object groupId; - - private final int messageCount; - - private final Date lastModified; - - private final Date expired; - - private final boolean discarded; - - public MessageGroupExpiredEvent(Object source, Object groupId, int messageCount, Date lastModified, Date expired, - boolean discarded) { - - super(source); - this.groupId = groupId; - this.messageCount = messageCount; - this.lastModified = (Date) lastModified.clone(); - this.expired = (Date) expired.clone(); - this.discarded = discarded; - } - - public Object getGroupId() { - return this.groupId; - } - - public int getMessageCount() { - return this.messageCount; - } - - protected Date getLastModified() { - return this.lastModified; - } - - public Date getExpired() { - return (Date) this.expired.clone(); - } - - public boolean isDiscarded() { - return this.discarded; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - Object sourceName; - if (this.source instanceof NamedComponent) { - sourceName = ((NamedComponent) source).getComponentName(); - } - else { - sourceName = this.source.toString(); - } - builder.append("MessageGroupExpiredEvent [groupId=") - .append(this.groupId) - .append(", messageCount=") - .append(this.messageCount) - .append(", lastModified=") - .append(this.lastModified) - .append(", expiredAt=") - .append(this.expired) - .append(", discarded=") - .append(this.discarded) - .append(", source=") - .append(sourceName) - .append("]"); - return builder.toString(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java deleted file mode 100644 index db5002a2455..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageGroupProcessor.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.store.MessageGroup; - -/** - * A processor for correlated groups of messages. - * - * @author Iwein Fuld - * - * @see org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler - */ -@FunctionalInterface -public interface MessageGroupProcessor { - - /** - * Process the given MessageGroup. Implementations are free to return as few or as many messages based on the - * invocation as needed. For example an aggregating processor will return only a single message representing the - * group, while a resequencing processor will return all messages whose preceding sequence has been satisfied. - *

If a multiple messages are returned the return value must be a Collection<Message>. - * @param group The message group. - * @return The result of processing the group. - */ - @Nullable - Object processMessageGroup(MessageGroup group); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageListProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageListProcessor.java deleted file mode 100644 index c512971084d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageListProcessor.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Collection; - -import org.springframework.messaging.Message; - -/** - * @author Dave Syer - * - */ -@FunctionalInterface -public interface MessageListProcessor { - - Object process(Collection> messages); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageSequenceComparator.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageSequenceComparator.java deleted file mode 100644 index 334ac67d1ee..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MessageSequenceComparator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.io.Serializable; -import java.util.Comparator; - -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.messaging.Message; - -/** - * @author Mark Fisher - * @author Dave Syer - * @author Artem Bilan - * @author Gary Russell - */ -@SuppressWarnings("serial") -public class MessageSequenceComparator implements Comparator>, Serializable { - - @Override - public int compare(Message o1, Message o2) { - int sequenceNumber1 = new IntegrationMessageHeaderAccessor(o1).getSequenceNumber(); - int sequenceNumber2 = new IntegrationMessageHeaderAccessor(o2).getSequenceNumber(); - - return Integer.compare(sequenceNumber1, sequenceNumber2); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingCorrelationStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingCorrelationStrategy.java deleted file mode 100644 index ef5e5c81216..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingCorrelationStrategy.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.lang.reflect.Method; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.handler.MethodInvokingMessageProcessor; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * {@link CorrelationStrategy} implementation that works as an adapter to another bean. - * - * @author Marius Bogoevici - * @author Dave Syer - * @author Artem Bilan - * @author Gary Russell - */ -public class MethodInvokingCorrelationStrategy implements CorrelationStrategy, BeanFactoryAware, ManageableLifecycle { - - private final MethodInvokingMessageProcessor processor; - - public MethodInvokingCorrelationStrategy(Object object, String methodName) { - this.processor = new MethodInvokingMessageProcessor(object, methodName); - } - - public MethodInvokingCorrelationStrategy(Object object, Method method) { - Assert.notNull(object, "'object' must not be null"); - Assert.notNull(method, "'method' must not be null"); - Assert.isTrue(!Void.TYPE.equals(method.getReturnType()), "Method return type must not be void"); - this.processor = new MethodInvokingMessageProcessor(object, method); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (beanFactory != null) { - this.processor.setBeanFactory(beanFactory); - } - } - - @Override - @Nullable - public Object getCorrelationKey(Message message) { - return this.processor.processMessage(message); - } - - @Override - public void start() { - this.processor.start(); - } - - @Override - public void stop() { - this.processor.stop(); - } - - @Override - public boolean isRunning() { - return this.processor.isRunning(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java deleted file mode 100644 index 1262a6cbab3..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessor.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.convert.ConversionService; -import org.springframework.integration.annotation.Aggregator; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * MessageGroupProcessor that serves as an adapter for the invocation of a POJO method. - * - * @author Iwein Fuld - * @author Mark Fisher - * @author Dave Syer - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor - implements ManageableLifecycle { - - private final MethodInvokingMessageListProcessor processor; - - /** - * Creates a wrapper around the object passed in. This constructor will look for a method that can process - * a list of messages. - * - * @param target the object to wrap - */ - public MethodInvokingMessageGroupProcessor(Object target) { - this.processor = new MethodInvokingMessageListProcessor(target, Aggregator.class); - } - - /** - * Creates a wrapper around the object passed in. This constructor will look for a named method specifically and - * fail when it cannot find a method with the given name. - * - * @param target the object to wrap - * @param methodName the name of the method to invoke - */ - public MethodInvokingMessageGroupProcessor(Object target, String methodName) { - this.processor = new MethodInvokingMessageListProcessor(target, methodName); - } - - /** - * Creates a wrapper around the object passed in. - * - * @param target the object to wrap - * @param method the method to invoke - */ - public MethodInvokingMessageGroupProcessor(Object target, Method method) { - this.processor = new MethodInvokingMessageListProcessor(target, method); - } - - public void setConversionService(ConversionService conversionService) { - this.processor.setConversionService(conversionService); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - super.setBeanFactory(beanFactory); - this.processor.setBeanFactory(beanFactory); - } - - @Override - protected final Object aggregatePayloads(MessageGroup group, Map headers) { - final Collection> messagesUpForProcessing = group.getMessages(); - Object object = this.processor.process(messagesUpForProcessing, headers); - Assert.state(object != null, "The process returned a null result. Null result is not expected."); - return object; - } - - @Override - public void start() { - this.processor.start(); - } - - @Override - public void stop() { - this.processor.stop(); - } - - @Override - public boolean isRunning() { - return this.processor.isRunning(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java deleted file mode 100644 index 2b69424f9f5..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingMessageListProcessor.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map; - -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.integration.handler.support.MessagingMethodInvokerHelper; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.integration.util.AbstractExpressionEvaluator; -import org.springframework.messaging.Message; - -/** - * A MessageListProcessor implementation that invokes a method on a target POJO. - * - * @param the method evaluation expecetd result type. - * - * @author Dave Syer - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.0 - */ -public class MethodInvokingMessageListProcessor extends AbstractExpressionEvaluator - implements ManageableLifecycle { - - private final MessagingMethodInvokerHelper delegate; - - public MethodInvokingMessageListProcessor(Object targetObject, Method method, Class expectedType) { - this.delegate = new MessagingMethodInvokerHelper(targetObject, method, expectedType, true); - } - - public MethodInvokingMessageListProcessor(Object targetObject, Method method) { - this.delegate = new MessagingMethodInvokerHelper(targetObject, method, true); - } - - public MethodInvokingMessageListProcessor(Object targetObject, String methodName, Class expectedType) { - this.delegate = new MessagingMethodInvokerHelper(targetObject, methodName, - expectedType, true); - } - - public MethodInvokingMessageListProcessor(Object targetObject, String methodName) { - this.delegate = new MessagingMethodInvokerHelper(targetObject, methodName, true); - } - - public MethodInvokingMessageListProcessor(Object targetObject, Class annotationType) { - this.delegate = new MessagingMethodInvokerHelper(targetObject, annotationType, Object.class, true); - } - - @Override - public void setBeanFactory(@NonNull BeanFactory beanFactory) { - super.setBeanFactory(beanFactory); - this.delegate.setBeanFactory(beanFactory); - } - - /** - * A {@code boolean} flag to use SpEL Expression evaluation or - * {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod} - * for target method invocation. - * @param useSpelInvoker to use SpEL Expression evaluation or not. - * @since 5.0 - */ - public void setUseSpelInvoker(boolean useSpelInvoker) { - this.delegate.setUseSpelInvoker(useSpelInvoker); - } - - @Override - public String toString() { - return this.delegate.toString(); - } - - @SuppressWarnings("unchecked") - public @Nullable T process(Collection> messages, @Nullable Map aggregateHeaders) { - return (T) this.delegate.process(messages, aggregateHeaders); - } - - @Override - public void start() { - this.delegate.start(); - } - - @Override - public void stop() { - this.delegate.stop(); - } - - @Override - public boolean isRunning() { - return this.delegate.isRunning(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java deleted file mode 100644 index 882c42b5a3e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/MethodInvokingReleaseStrategy.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.lang.reflect.Method; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.core.convert.ConversionService; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.support.management.ManageableLifecycle; - -/** - * A {@link ReleaseStrategy} that invokes a method on a plain old Java object. - * - * @author Marius Bogoevici - * @author Dave Syer - * @author Artem Bilan - */ -public class MethodInvokingReleaseStrategy implements ReleaseStrategy, BeanFactoryAware, ManageableLifecycle { - - private final MethodInvokingMessageListProcessor adapter; - - public MethodInvokingReleaseStrategy(Object object, Method method) { - this.adapter = new MethodInvokingMessageListProcessor(object, method, Boolean.class); - } - - public MethodInvokingReleaseStrategy(Object object, String methodName) { - this.adapter = new MethodInvokingMessageListProcessor(object, methodName, Boolean.class); - } - - public void setConversionService(ConversionService conversionService) { - this.adapter.setConversionService(conversionService); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - this.adapter.setBeanFactory(beanFactory); - } - - @Override - public boolean canRelease(MessageGroup messages) { - return Boolean.TRUE.equals(this.adapter.process(messages.getMessages(), null)); - } - - @Override - public void start() { - this.adapter.start(); - } - - @Override - public void stop() { - this.adapter.stop(); - } - - @Override - public boolean isRunning() { - return this.adapter.isRunning(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ReleaseStrategy.java deleted file mode 100644 index 532fbdbda8b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ReleaseStrategy.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.springframework.integration.store.MessageGroup; - -/** - * Strategy for determining when a group of messages reaches a state of - * completion (i.e. can trip a barrier). - * - * @author Mark Fisher - * @author Dave Syer - */ -@FunctionalInterface -public interface ReleaseStrategy { - - boolean canRelease(MessageGroup group); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java deleted file mode 100644 index a8d1b06b682..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageGroupProcessor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.store.MessageGroup; -import org.springframework.messaging.Message; - -/** - * This class implements all the strategy interfaces needed for a default resequencer. - * - * @author Iwein Fuld - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 2.0 - */ -public class ResequencingMessageGroupProcessor implements MessageGroupProcessor { - - private final Comparator> comparator = new MessageSequenceComparator(); - - @Nullable - public Object processMessageGroup(MessageGroup group) { - Collection> messages = group.getMessages(); - - if (!messages.isEmpty()) { - List> sorted = new ArrayList<>(messages); - sorted.sort(this.comparator); - ArrayList> partialSequence = new ArrayList<>(); - int previousSequence = extractSequenceNumber(sorted.get(0)); - int currentSequence = previousSequence; - for (Message message : sorted) { - previousSequence = currentSequence; - currentSequence = extractSequenceNumber(message); - if (currentSequence - 1 > previousSequence) { - //there is a gap in the sequence here - break; - } - partialSequence.add(message); - } - - return partialSequence; - } - return null; - } - - private Integer extractSequenceNumber(Message message) { - return StaticMessageHeaderAccessor.getSequenceNumber(message); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageHandler.java deleted file mode 100644 index b23ee61686d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ResequencingMessageHandler.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Collection; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.store.SimpleMessageStore; -import org.springframework.messaging.Message; - -/** - * Resequencer specific implementation of {@link AbstractCorrelatingMessageHandler}. - * Will remove {@link MessageGroup}s only if 'sequenceSize' is provided and reached. - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class ResequencingMessageHandler extends AbstractCorrelatingMessageHandler { - - public ResequencingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store, - CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) { - - super(processor, store, correlationStrategy, releaseStrategy); - this.setExpireGroupsUponTimeout(false); - } - - public ResequencingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store) { - super(processor, store); - this.setExpireGroupsUponTimeout(false); - } - - public ResequencingMessageHandler(MessageGroupProcessor processor) { - super(processor); - this.setExpireGroupsUponTimeout(false); - } - - /** - * Overridden to false for a resequencer so late messages are immediately discarded rather - * than waiting for the next timeout. - */ - @Override - public final void setExpireGroupsUponTimeout(boolean expireGroupsUponTimeout) { - super.setExpireGroupsUponTimeout(expireGroupsUponTimeout); - } - - @Override - public String getComponentType() { - return "resequencer"; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.resequencer; - } - - @Override - protected boolean shouldCopyRequestHeaders() { - return false; - } - - @Override - protected void afterRelease(MessageGroup messageGroup, @Nullable Collection> completedMessages) { - afterRelease(messageGroup, completedMessages, false); - } - - /** - * Perform group removal if its {@code size} is equal to the {@code sequenceSize}. - * Remove {@code completedMessages} from the group if it isn't null. - * @param messageGroup the group to clean up. - * @param completedMessages The completed messages. - * @param timeout True if the release/discard was due to a timeout. - */ - @Override - protected void afterRelease(MessageGroup messageGroup, @Nullable Collection> completedMessages, boolean timeout) { - int size = messageGroup.size(); - int sequenceSize = messageGroup.getSequenceSize(); - - // If there is no sequence then it must be incomplete or unbounded - if (sequenceSize > 0 && sequenceSize == size) { - remove(messageGroup); - } - else { - Object groupId = messageGroup.getGroupId(); - MessageGroupStore messageStore = getMessageStore(); - if (completedMessages != null) { - int lastReleasedSequenceNumber = findLastReleasedSequenceNumber(groupId, completedMessages); - messageStore.setLastReleasedSequenceNumberForGroup(groupId, lastReleasedSequenceNumber); - if (messageStore instanceof SimpleMessageStore - && completedMessages.size() == messageGroup.size()) { - ((SimpleMessageStore) messageStore).clearMessageGroup(groupId); - } - else { - messageStore.removeMessagesFromGroup(groupId, completedMessages); - } - } - if (timeout) { - messageStore.completeGroup(groupId); - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java deleted file mode 100644 index dcaf6af8dc4..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.store.MessageGroup; -import org.springframework.messaging.Message; - -/** - * An implementation of {@link ReleaseStrategy} that simply compares the current size of - * the message list to the expected 'sequenceSize'. Supports release of partial sequences. - * Correlating message handlers prevent the addition of duplicate sequences to the group. - * - * @author Mark Fisher - * @author Marius Bogoevici - * @author Dave Syer - * @author Iwein Fuld - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Enrique Rodriguez - */ -public class SequenceSizeReleaseStrategy implements ReleaseStrategy { - - private static final Log LOGGER = LogFactory.getLog(SequenceSizeReleaseStrategy.class); - - private final Comparator> comparator = new MessageSequenceComparator(); - - private volatile boolean releasePartialSequences; - - /** - * Construct an instance that does not support releasing partial sequences. - */ - public SequenceSizeReleaseStrategy() { - this(false); - } - - /** - * Construct an instance that supports releasing partial sequences if - * releasePartialSequences is true. This can be an expensive operation on large - * groups. - * @param releasePartialSequences true to allow the release of partial sequences. - */ - public SequenceSizeReleaseStrategy(boolean releasePartialSequences) { - this.releasePartialSequences = releasePartialSequences; - } - - /** - * Flag that determines if partial sequences are allowed. If true then as soon as - * enough messages arrive that can be ordered they will be released, provided they - * all have sequence numbers greater than those already released. - * This can be an expensive operation for large groups. - * @param releasePartialSequences true when partial sequences should be released. - */ - public void setReleasePartialSequences(boolean releasePartialSequences) { - this.releasePartialSequences = releasePartialSequences; - } - - @Override - public boolean canRelease(MessageGroup messageGroup) { - boolean canRelease = false; - int size = messageGroup.size(); - if (this.releasePartialSequences && size > 0) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Considering partial release of group [" + messageGroup + "]"); - } - Collection> messages = messageGroup.getMessages(); - Message minMessage = Collections.min(messages, this.comparator); - - int nextSequenceNumber = StaticMessageHeaderAccessor.getSequenceNumber(minMessage); - int lastReleasedMessageSequence = messageGroup.getLastReleasedMessageSequenceNumber(); - - if (nextSequenceNumber - lastReleasedMessageSequence == 1) { - canRelease = true; - } - } - else { - if (size == 0) { - canRelease = true; - } - else { - int sequenceSize = messageGroup.getSequenceSize(); - // If there is no sequence then it must be incomplete.... - if (sequenceSize == size) { - canRelease = true; - } - } - } - return canRelease; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SimpleMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SimpleMessageGroupProcessor.java deleted file mode 100644 index a0efa6e6f66..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SimpleMessageGroupProcessor.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.springframework.integration.store.MessageGroup; - -/** - * A {@link MessageGroupProcessor} that simply returns the messages in the group. - * It can be used to configure an aggregator as a barrier, such that when the group - * is complete, the grouped messages are released as individual messages. - * - * @author Gary Russell - * @since 4.2 - * - */ -public class SimpleMessageGroupProcessor implements MessageGroupProcessor { - - @Override - public Object processMessageGroup(MessageGroup group) { - return group.getMessages(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SimpleSequenceSizeReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SimpleSequenceSizeReleaseStrategy.java deleted file mode 100644 index 5e04f75b1a2..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SimpleSequenceSizeReleaseStrategy.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.springframework.integration.store.MessageGroup; - -/** - * An implementation of {@link ReleaseStrategy} that simply compares the current size of - * the message list to the expected 'sequenceSize'. It does not support releasing partial - * sequences. Correlating message handlers using this strategy do not check for duplicate - * sequence numbers. - * @author Gary Russell - * @since 4.3.4 - * - */ -public class SimpleSequenceSizeReleaseStrategy implements ReleaseStrategy { - - @Override - public boolean canRelease(MessageGroup group) { - return group.getSequenceSize() == group.size(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/TimeoutCountSequenceSizeReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/TimeoutCountSequenceSizeReleaseStrategy.java deleted file mode 100755 index b69c7abdebb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/TimeoutCountSequenceSizeReleaseStrategy.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aggregator; - -import org.springframework.integration.store.MessageGroup; -import org.springframework.messaging.Message; - -/** - * A {@link ReleaseStrategy} that releases all messages if any of the following is true: - * - *
    - *
  • The sequence is complete (if there is one).
  • - *
  • There are more messages than a threshold set by the user.
  • - *
  • The time elapsed since the earliest message, according to their timestamps, if - * present, exceeds a timeout set by the user.
  • - *
- * - * @author Dave Syer - * @author Gary Russell - * @author Peter Uhlenbruck - * - * @since 2.0 - */ -public class TimeoutCountSequenceSizeReleaseStrategy implements ReleaseStrategy { - - /** - * Default timeout is one minute. - */ - public static final long DEFAULT_TIMEOUT = 60 * 1000; - - /** - * Default threshold is effectively infinite. - */ - public static final int DEFAULT_THRESHOLD = Integer.MAX_VALUE; - - private final int threshold; - - private final long timeout; - - public TimeoutCountSequenceSizeReleaseStrategy() { - this(DEFAULT_THRESHOLD, DEFAULT_TIMEOUT); - } - - /** - * @param threshold the number of messages to accept before releasing - * @param timeout the timeout for the release in milliseconds - */ - public TimeoutCountSequenceSizeReleaseStrategy(int threshold, long timeout) { - this.threshold = threshold; - this.timeout = timeout; - } - - @Override - public boolean canRelease(MessageGroup messages) { - long elapsedTime = System.currentTimeMillis() - findEarliestTimestamp(messages); - return messages.isComplete() || messages.getMessages().size() >= this.threshold || elapsedTime > this.timeout; - } - - /** - * @param messages the message group - * @return the earliest timestamp or Long.MAX_VALUE - */ - private long findEarliestTimestamp(MessageGroup messages) { - long result = Long.MAX_VALUE; - for (Message message : messages.getMessages()) { - Long timestamp = message.getHeaders().getTimestamp(); - if (timestamp != null && timestamp < result) { - result = timestamp; - } - } - return result; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/package-info.java deleted file mode 100644 index 7deb51b0ff1..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to message aggregation. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.aggregator; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java deleted file mode 100644 index e758275f7cb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of aggregating messages. - *

- * A method annotated with @Aggregator may accept a collection - * of Messages or Message payloads and should return a single - * Message or a single Object to be used as a Message payload. - * - * @author Marius Bogoevici - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Chris Bono - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(Aggregators.class) -public @interface Aggregator { - - /** - * @return The channel name for receiving messages to be aggregated - */ - String inputChannel() default ""; - - /** - * @return The channel name for sending aggregated result messages - */ - String outputChannel() default ""; - - /** - * @return The channel name for sending discarded messages (due to a timeout) - */ - String discardChannel() default ""; - - /** - * Specify the maximum amount of time in milliseconds to wait when sending a reply - * {@link org.springframework.messaging.Message} to the {@link #outputChannel()}. - * Defaults to {@code 30} seconds. - * It is applied only if the output channel has some 'sending' limitations, e.g. - * {@link org.springframework.integration.channel.QueueChannel} with - * a fixed 'capacity' and is currently full. - * In this case a {@link org.springframework.messaging.MessageDeliveryException} is thrown. - * The 'sendTimeout' is ignored in case of - * {@link org.springframework.integration.channel.AbstractSubscribableChannel} implementations. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendTimeout}}. - * @return The timeout for sending results to the reply target (in milliseconds) - */ - String sendTimeout() default ""; - - /** - * Specify whether messages that expired should be aggregated and sent to the {@link #outputChannel()} - * or {@code replyChannel} from message headers. Messages are expired when their containing - * {@link org.springframework.integration.store.MessageGroup} expires. One of the ways of expiring MessageGroups - * is by configuring a {@link org.springframework.integration.store.MessageGroupStoreReaper}. - * However, MessageGroups can alternatively be expired by simply calling - * {@code MessageGroupStore.expireMessageGroup(groupId)}. That could be accomplished via a ControlBus operation - * or by simply invoking that method if you have a reference to the - * {@link org.springframework.integration.store.MessageGroupStore} instance. - * Defaults to {@code false}. - * * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendPartialResultsOnExpiry}}. - * @return Indicates whether to send an incomplete aggregate on expiry of the message group - */ - String sendPartialResultsOnExpiry() default ""; - - /** - * The {@link org.springframework.context.SmartLifecycle} {@code autoStartup} option. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * Defaults to {@code true}. - * @return the auto startup {@code boolean} flag. - */ - String autoStartup() default ""; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregators.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregators.java deleted file mode 100644 index a6a1a5f5670..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregators.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link Aggregator} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Aggregators { - - Aggregator[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/AnnotationConstants.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/AnnotationConstants.java deleted file mode 100644 index 8a297e01997..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/AnnotationConstants.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -/** - * Common value constants for annotation attributes. - * - * @author Gary Russell - * @since 4.1 - * - */ -public final class AnnotationConstants { - - /** - * Constant defining a value as a replacement for {@code null} which - * we cannot use in annotation attributes. - */ - public static final String NULL = "__NULL__"; - - private AnnotationConstants() { - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java deleted file mode 100644 index 42e7bea9d82..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Messaging Annotation to mark a {@link org.springframework.context.annotation.Bean} - * method for a {@link org.springframework.messaging.MessageChannel} to produce a - * {@link org.springframework.integration.handler.BridgeHandler} and Consumer Endpoint. - *

- * The {@code inputChannel} for the {@link org.springframework.integration.endpoint.AbstractEndpoint} - * is the {@link #value()} of this annotation and determines the type of endpoint - - * {@link org.springframework.integration.endpoint.EventDrivenConsumer} or - * {@link org.springframework.integration.endpoint.PollingConsumer}. - *

- * The {@link org.springframework.messaging.MessageChannel} {@link org.springframework.context.annotation.Bean} - * is used as the {@code outputChannel} of the {@link org.springframework.integration.handler.BridgeHandler}. - * - * @author Artem Bilan - * @author Chris Bono - * - * @since 4.0 - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(BridgeFromRepeatable.class) -public @interface BridgeFrom { - - /** - * @return the inbound channel name to receive message for the - * {@link org.springframework.integration.handler.BridgeHandler} - */ - String value(); - - /* - {@code SmartLifecycle} options. - Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - */ - String autoStartup() default "true"; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFromRepeatable.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFromRepeatable.java deleted file mode 100644 index 01b5501cf01..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFromRepeatable.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link BridgeFrom} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface BridgeFromRepeatable { - - BridgeFrom[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java deleted file mode 100644 index 3040c514dc5..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Messaging Annotation to mark a {@link org.springframework.context.annotation.Bean} - * method for a {@link org.springframework.messaging.MessageChannel} to produce a - * {@link org.springframework.integration.handler.BridgeHandler} and Consumer Endpoint. - *

- * The {@link org.springframework.messaging.MessageChannel} {@link org.springframework.context.annotation.Bean} - * marked with this annotation is used as the {@code inputChannel} for the - * {@link org.springframework.integration.endpoint.AbstractEndpoint} - * and determines the type of endpoint - - * {@link org.springframework.integration.endpoint.EventDrivenConsumer} or - * {@link org.springframework.integration.endpoint.PollingConsumer}. - *

- * The {@link #value()} of this annotation is the {@code outputChannel} for the - * {@link org.springframework.integration.handler.BridgeHandler}. - * If it isn't present, the {@link org.springframework.integration.handler.BridgeHandler} - * sends the message to the {@code reply-channel} in its message headers, if present. - * If no output channel is provided and no reply-channel exists, an exception is thrown. - * - * @author Artem Bilan - * @author Chris Bono - * - * @since 4.0 - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(BridgeToRepeatable.class) -public @interface BridgeTo { - - /** - * @return the outbound channel name to send the message to for the - * {@link org.springframework.integration.handler.BridgeHandler} reply. - * Optional: when omitted the message is sent to the {@code reply-channel} - * in its headers (if present - an exception is thrown otherwise). - */ - String value() default ""; - - /* - {@code SmartLifecycle} options. - Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - */ - String autoStartup() default "true"; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeToRepeatable.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeToRepeatable.java deleted file mode 100644 index d1cfdb0f65a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeToRepeatable.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link BridgeTo} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface BridgeToRepeatable { - - BridgeTo[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/CorrelationStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/CorrelationStrategy.java deleted file mode 100644 index c5fc0db7b01..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/CorrelationStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * Indicates that a given method is capable of determining the correlation key - * of a message sent as parameter. - * - * @author Marius Bogoevici - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Documented -public @interface CorrelationStrategy { - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Default.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Default.java deleted file mode 100644 index dd3c9d9804d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Default.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * Indicates that the class member has some default meaning. - *

- * The target parsing logic may vary. - * One of the use-cases is service with several methods which are - * selected for invocation at runtime by some condition. - * The method with this {@link Default} annotation may mean - * a fallback option when no one other method meets condition. - * - * @author Artem Bilan - * - * @since 5.0 - */ -@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Default { - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/EndpointId.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/EndpointId.java deleted file mode 100644 index 1fb9ed26847..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/EndpointId.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * When used alongside an EIP annotation (and no {@code @Bean}), specifies the bean name of - * the consumer bean with the handler bean being {@code id.handler} (for a consuming - * endpoint) or {@code id.source} for a message source (e.g. inbound channel adapter). - *

- * When there is also a {@code @Bean} annotation, this is the name of the consumer or - * source polling bean (the handler or source gets the normal {@code @Bean} name). When - * using on a {@code MessageHandler @Bean}, it is recommended to name the bean - * {@code foo.handler} when using {@code @EndpointId("foo"}. This will align with - * conventions in the framework. Similarly, for a message source, use - * {@code @Bean("bar.source"} and {@code @EndpointId("bar")}. - *

- * This is not allowed if there are multiple EIP annotations on the same method. - * - * @author Gary Russell - * - * @since 5.0.4 - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface EndpointId { - - /** - * @return the id - */ - String value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java deleted file mode 100644 index 3a6e00e2627..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of playing the role of a Message Filter. - *

- * A method annotated with @Filter may accept a parameter of type - * {@link org.springframework.messaging.Message} or of the expected - * Message payload's type. Any type conversion supported by default or any - * Converters registered with the "integrationConversionService" bean will be - * applied to the Message payload if necessary. Header values can also be passed - * as Message parameters by using the - * {@link org.springframework.messaging.handler.annotation.Header @Header} parameter annotation. - *

- * The return type of the annotated method must be a boolean (or Boolean). - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Chris Bono - * - * @since 2.0 - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(Filters.class) -public @interface Filter { - - /** - * Specify the channel from which this filter will consume messages. - * If the channel does not exist, a {@code DirectChannel} with this name will be - * registered in the application context. - * @return The channel name. - */ - String inputChannel() default ""; - - /** - * Specify the channel to which this filter will send messages that pass the - * selector. - * @return The channel name. - */ - String outputChannel() default ""; - - /** - * Specify the channel to which this filter will send messages that do no pass the - * selector. - * @return The channel name. - */ - String discardChannel() default ""; - - /** - * Throw an exception if the filter rejects the message. - * Defaults to {@code false}. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.throwExceptionOnRejection}}. - * @return the throw Exception on rejection flag. - */ - String throwExceptionOnRejection() default ""; - - /** - * Specify a "chain" of {@code Advice} beans that will "wrap" the message handler. - * Only the handler is advised, not the downstream flow. - * @return the advice chain. - */ - String[] adviceChain() default {}; - - /** - * When {@code true} (default) any discard action (and exception thrown) will occur - * within the scope of the advice class(es) in the chain. Otherwise, these actions - * will occur after the advice chain returns. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.discardWithinAdvice}}. - * @return the discard within advice flag. - */ - String discardWithinAdvice() default ""; - - /** - * Specify the maximum amount of time in milliseconds to wait when sending a reply - * {@link org.springframework.messaging.Message} to the {@link #outputChannel()}. - * Defaults to {@code 30} seconds. - * It is applied only if the output channel has some 'sending' limitations, e.g. - * {@link org.springframework.integration.channel.QueueChannel} with - * fixed a 'capacity'. In this case a {@link org.springframework.messaging.MessageDeliveryException} is thrown. - * The 'sendTimeout' is ignored in case of - * {@link org.springframework.integration.channel.AbstractSubscribableChannel} implementations. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendTimeout}}. - * @return The timeout for sending results to the reply target (in milliseconds) - */ - String sendTimeout() default ""; - - /** - * The {@link org.springframework.context.SmartLifecycle} {@code autoStartup} option. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * Defaults to {@code true}. - * @return the auto startup {@code boolean} flag. - */ - String autoStartup() default ""; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filters.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filters.java deleted file mode 100644 index 9584019b1a9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filters.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link Filter} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Filters { - - Filter[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java deleted file mode 100644 index 05f7e002623..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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.integration.context.IntegrationContextUtils; - -/** - * Indicates that an interface method is capable of mapping its parameters - * to a message or message payload. These method-level annotations are detected - * by the {@link org.springframework.integration.gateway.GatewayProxyFactoryBean} - * where the annotation attributes can override the default channel settings. - * - *

A method annotated with @Gateway may accept a single non-annotated - * parameter of type {@link org.springframework.messaging.Message} - * or of the intended Message payload type. Method parameters may be mapped - * to individual Message header values by using the - * {@link org.springframework.messaging.handler.annotation.Header @Header} - * parameter annotation. Alternatively, to pass the entire Message headers - * map, a Map-typed parameter may be annotated with - * {@link org.springframework.messaging.handler.annotation.Headers}. - * - *

Return values from the annotated method may be of any type. If the - * declared return value is not a Message, the reply Message's payload will be - * returned and any type conversion as supported by Spring's - * {@link org.springframework.beans.SimpleTypeConverter} will be applied to - * the return value if necessary. - * - *

Note: unlike @Publisher, this annotation is for exposing a - * Messaging Endpoint based on a Proxy for the marked interface method. - * The method invocation causes messaging interaction using an - * AOP Advice. Method parameters become the part of sent message (payload, headers). - * The method return value is the result (payload) of the messaging flow invoked by the - * Proxy. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @see MessagingGateway - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Gateway { - - /** - * Specify the channel to which messages will be sent; overrides the encompassing - * gateway's default request channel. - * @return the channel name. - */ - String requestChannel() default ""; - - /** - * Specify the channel from which reply messages will be received; overrides the - * encompassing gateway's default reply channel. - * @return the channel name. - */ - String replyChannel() default ""; - - /** - * Specify the timeout (ms) when sending to the request channel - only applies if the - * send might block (such as a bounded {@code QueueChannel} that is currently full. - * Overrides the encompassing gateway's default request timeout. - * @return the timeout. - * @see #requestTimeoutExpression() - */ - long requestTimeout() default IntegrationContextUtils.DEFAULT_TIMEOUT; - - /** - * Specify a SpEL Expression to determine the timeout (ms) when sending to the request - * channel - only applies if the send might block (such as a bounded - * {@code QueueChannel} that is currently full. Overrides the encompassing gateway's - * default request timeout. Overrides {@link #requestTimeout()}. - * @return the timeout. - * @since 5.0 - */ - String requestTimeoutExpression() default ""; - - /** - * Specify the time (ms) that the thread sending the request will wait for a reply. - * The timer starts when the thread returns to the gateway, not when the request - * message is sent. Overrides the encompassing gateway's default reply timeout. - * @return the timeout. - * @see #replyTimeoutExpression() - */ - long replyTimeout() default IntegrationContextUtils.DEFAULT_TIMEOUT; - - /** - * Specify a SpEL Expression to determine the time (ms) that the thread sending - * the request will wait for a reply. The timer starts when the thread returns to the - * gateway, not when the request message is sent. Overrides the encompassing gateway's - * default reply timeout. Overrides {@link #replyTimeout()}. - * @return the timeout. - * @since 5.0 - */ - String replyTimeoutExpression() default ""; - - /** - * Specify a SpEL expression to determine the payload of the request message. - * @return the expression. - */ - String payloadExpression() default ""; - - /** - * Specify additional headers that will be added to the request message. - * @return the headers. - */ - GatewayHeader[] headers() default {}; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/GatewayHeader.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/GatewayHeader.java deleted file mode 100644 index 764ced6dbe5..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/GatewayHeader.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Provides the message header {@code value} or {@code expression}. - * - * @author Artem Bilan - * @since 4.0 - */ -@Target({}) -@Retention(RetentionPolicy.RUNTIME) -public @interface GatewayHeader { - - /** - * @return The name of the header. - */ - String name(); - - /** - * @return The value for the header. - */ - String value() default ""; - - /** - * @return The {@code Expression} to be evaluated to produce a value for the header. - */ - String expression() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/IdempotentReceiver.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/IdempotentReceiver.java deleted file mode 100644 index 693f3780cdf..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/IdempotentReceiver.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * A {@code method} that has a MessagingAnnotation (@code @ServiceActivator, @Router etc.) - * that also has this annotation, has an - * {@link org.springframework.integration.handler.advice.IdempotentReceiverInterceptor} applied - * to the associated {@link org.springframework.messaging.MessageHandler#handleMessage} method. - * The interceptor bean names are provided in the {@link #value()}. - * - * @author Artem Bilan - * @since 4.1 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IdempotentReceiver { - - /** - * @return the {@link org.springframework.integration.handler.advice.IdempotentReceiverInterceptor} - * bean references. - */ - String[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java deleted file mode 100644 index fa688e74db7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of producing a {@link org.springframework.messaging.Message} - * or {@link org.springframework.messaging.Message} {@code payload}. - *

- * A method annotated with {@code @InboundChannelAdapter} can't accept any parameters. - *

- * Return values from the annotated method may be of any type. If the return - * value is not a {@link org.springframework.messaging.Message}, a {@link org.springframework.messaging.Message} - * will be created with that object as its {@code payload}. - *

- * The result {@link org.springframework.messaging.Message} will be sent to the provided {@link #value()}. - *

- * {@code @InboundChannelAdapter} is an analogue of {@code }. With that - * the {@link org.springframework.integration.scheduling.PollerMetadata} is required to initiate - * the method invocation. Or {@link #poller()} should be provided, or the - * {@link org.springframework.integration.scheduling.PollerMetadata#DEFAULT_POLLER} bean has to be configured - * in the application context. - * - * - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * - * @since 4.0 - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(InboundChannelAdapters.class) -public @interface InboundChannelAdapter { - - /** - * Alias for the {@link #channel()} attribute. - * @return the 'channel' bean name to send the {@link org.springframework.messaging.Message}. - */ - @AliasFor("channel") - String value() default ""; - - /** - * @return the 'channel' bean name to send the {@link org.springframework.messaging.Message}. - * @since 4.2.6 - */ - @AliasFor("value") - String channel() default ""; - - /** - * {@code SmartLifecycle} options. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * @return if the channel adapter is started automatically or not. - */ - String autoStartup() default "true"; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link org.springframework.integration.annotation.Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * NOTE: a {@link Poller} here has {@link Poller#maxMessagesPerPoll()} set to 1 by default. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapters.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapters.java deleted file mode 100644 index 9a4583552b4..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapters.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link InboundChannelAdapter} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface InboundChannelAdapters { - - InboundChannelAdapter[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java deleted file mode 100644 index 4051545199a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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.support.BeanNameGenerator; -import org.springframework.context.annotation.ComponentScan.Filter; -import org.springframework.context.annotation.Import; -import org.springframework.core.annotation.AliasFor; -import org.springframework.integration.config.IntegrationComponentScanRegistrar; - -/** - * Configures component scanning directives for use with - * {@link org.springframework.context.annotation.Configuration} classes. - *

- * Scans for {@link MessagingGateway} on interfaces to create {@code GatewayProxyFactoryBean}s. - * - * @author Artem Bilan - * @since 4.0 - * - * @see org.springframework.context.annotation.ComponentScan - * @see MessagingGateway - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented -@Import(IntegrationComponentScanRegistrar.class) -public @interface IntegrationComponentScan { - - /** - * Alias for the {@link #basePackages()} attribute. - * Allows for more concise annotation declarations e.g.: - * {@code @IntegrationComponentScan("org.my.pkg")} instead of - * {@code @IntegrationComponentScan(basePackages="org.my.pkg")}. - * @return the array of 'basePackages'. - */ - @AliasFor("basePackages") - String[] value() default {}; - - /** - * Base packages to scan for annotated components. The {@link #value()} is an alias - * for (and mutually exclusive with) this attribute. Use {@link #basePackageClasses()} - * for a type-safe alternative to String-based package names. - * @return the array of 'basePackages'. - */ - @AliasFor("value") - String[] basePackages() default {}; - - /** - * Type-safe alternative to {@link #basePackages()} for specifying the packages to - * scan for annotated components. The package of each class specified will be scanned. - * Consider creating a special no-op marker class or interface in each package that - * serves no purpose other than being referenced by this attribute. - * @return the array of 'basePackageClasses'. - */ - Class[] basePackageClasses() default {}; - - /** - * Indicates whether automatic detection of classes annotated with - * {@code @MessagingGateway} should be enabled. - * @return the {@code useDefaultFilters} flag - * @since 5.0 - */ - boolean useDefaultFilters() default true; - - /** - * Specifies which types are eligible for component scanning. - *

Further narrows the set of candidate components from everything in - * {@link #basePackages} to everything in the base packages that matches the given - * filter or filters. - *

Note that these filters will be applied in addition to the default filters, if - * specified. Any type under the specified base packages which matches a given filter - * will be included, even if it does not match the default filters (i.e. is not - * annotated with {@code @MessagingGateway}). - * @return the {@code includeFilters} array - * @since 5.0 - * @see #excludeFilters() - */ - Filter[] includeFilters() default {}; - - /** - * Specifies which types are not eligible for component scanning. - * @return the {@code excludeFilters} array - * @since 5.0 - * @see #includeFilters() - */ - Filter[] excludeFilters() default {}; - - /** - * The {@link BeanNameGenerator} class to be used for naming detected Spring Integration components. - *

The default value of the {@link BeanNameGenerator} interface itself indicates - * that the scanner used to process this {@link IntegrationComponentScan} annotation should - * use its inherited bean name generator, e.g. the default - * {@link org.springframework.context.annotation.AnnotationBeanNameGenerator} - * or any custom instance supplied to the application context at bootstrap time. - * @since 6.0 - * @see org.springframework.context.annotation.ComponentScan#nameGenerator() - */ - Class nameGenerator() default BeanNameGenerator.class; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessageEndpoint.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessageEndpoint.java deleted file mode 100644 index e50ee5520e8..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessageEndpoint.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; -import org.springframework.stereotype.Component; - -/** - * Stereotype annotation indicating that a class is capable of serving as a - * Message Endpoint. - * - * @author Mark Fisher - * @author Artem Bilan - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -@Component -public @interface MessageEndpoint { - - /** - * The value may indicate a suggestion for a logical component name, - * to be turned into a Spring bean in case of an autodetected component. - * @return the suggested component name, if any - */ - @AliasFor(annotation = Component.class) - String value() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java deleted file mode 100644 index d826656ddae..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.gateway.MessagingGatewaySupport; - -/** - * A stereotype annotation to provide an Integration Messaging Gateway Proxy - * as an abstraction over the messaging API. The target application's - * business logic may be completely unaware of the Spring Integration - * API, with the code interacting only via the interface. - *

- * Important: The {@link IntegrationComponentScan} annotation is required along with - * {@link org.springframework.context.annotation.Configuration} - * to scan interfaces annotated with {@link MessagingGateway}, because the - * standard {@link org.springframework.context.annotation.ComponentScan} - * ignores interfaces. - *

- * The {@link Gateway} annotation can be used for the per interface method configuration. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.0 - * - * @see IntegrationComponentScan - * @see MessageEndpoint - * @see Gateway - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@MessageEndpoint -public @interface MessagingGateway { - - /** - * The value may indicate a suggestion for a logical component name, - * to be turned into a Spring bean in case of an autodetected component. - * @return the suggested component name, if any - * @since 6.0 - */ - @AliasFor(annotation = MessageEndpoint.class) - String value() default ""; - - /** - * The value may indicate a suggestion for a logical component name, - * to be turned into a Spring bean in case of an autodetected component. - * @return the suggested component name, if any - */ - @AliasFor(annotation = MessageEndpoint.class, attribute = "value") - String name() default ""; - - /** - * Identifies the default channel to which messages will be sent upon invocation - * of methods of the gateway proxy. - * See {@link Gateway#requestChannel()} for per-method configuration. - * @return the suggested channel name, if any - */ - String defaultRequestChannel() default ""; - - /** - * Identifies the default channel the gateway proxy will subscribe to, to receive reply - * {@code Message}s, the payloads of - * which will be converted to the return type of the method signature. - * See {@link Gateway#replyChannel()} for per-method configuration. - * @return the suggested channel name, if any - */ - String defaultReplyChannel() default ""; - - /** - * Identifies a channel that error messages will be sent to if a failure occurs in the - * gateway's proxy invocation. If no {@code errorChannel} reference is provided, the gateway will - * propagate {@code Exception}s to the caller. To completely suppress {@code Exception}s, provide a - * reference to the {@code nullChannel} here. - * @return the suggested channel name, if any - */ - String errorChannel() default ""; - - /** - * Provides the amount of time dispatcher would wait to send a {@code Message}. This - * timeout would only apply if there is a potential to block in the send call. For - * example if this gateway is hooked up to a {@code QueueChannel}. Value is specified - * in milliseconds; it can be a simple long value or a SpEL expression; array variable - * #args is available. - * See {@link Gateway#requestTimeout()} for per-method configuration. - * @return the suggested timeout in milliseconds, if any - */ - String defaultRequestTimeout() default IntegrationContextUtils.DEFAULT_TIMEOUT_STRING; - - /** - * Allows to specify how long this gateway will wait for the reply {@code Message} - * before returning. The {@code null} is returned if the gateway times out. - * Value is specified in milliseconds; it can be a simple long - * value or a SpEL expression; array variable #args is available. - * See {@link Gateway#replyTimeout()} for per-method configuration. - * @return the suggested timeout in milliseconds, if any - */ - String defaultReplyTimeout() default IntegrationContextUtils.DEFAULT_TIMEOUT_STRING; - - /** - * Provide a reference to an implementation of {@link java.util.concurrent.Executor} - * to use for any of the interface methods that have a {@link java.util.concurrent.Future} return type. - * This {@code Executor} will only be used for those async methods; the sync methods - * will be invoked in the caller's thread. - * Use {@link AnnotationConstants#NULL} to specify no async executor - for example - * if your downstream flow returns a {@link java.util.concurrent.Future}. - * @return the suggested executor bean name, if any - */ - String asyncExecutor() default ""; - - /** - * An expression that will be used to generate the {@code payload} for all methods in the service interface - * unless explicitly overridden by a method declaration. Variables include {@code #args}, {@code #methodName}, - * {@code #methodString} and {@code #methodObject}; - * a bean resolver is also available, enabling expressions like {@code @someBean(#args)}. - * See {@link Gateway#payloadExpression()} for per-method configuration. - * @return the suggested payload expression, if any - */ - String defaultPayloadExpression() default ""; - - /** - * Provides custom message headers. These default headers are created for - * all methods on the service-interface (unless overridden by a specific method). - * See {@link Gateway#headers()} for per-method configuration. - * @return the suggested payload expression, if any - */ - GatewayHeader[] defaultHeaders() default {}; - - /** - * An {@link org.springframework.integration.gateway.MethodArgsMessageMapper} - * to map the method arguments to a {@link org.springframework.messaging.Message}. When this - * is provided, no {@code payload-expression}s or {@code header}s are allowed; the custom mapper is - * responsible for creating the message. - * @return the suggested mapper bean name, if any - */ - String mapper() default ""; - - /** - * Indicate if {@code default} methods on the interface should be proxied as well. - * If an explicit {@link Gateway} annotation is present on method it is proxied - * independently of this option. - * Note: default methods in JDK classes (such as {@code Function}) can be proxied, but cannot be invoked - * via {@code MethodHandle} by an internal Java security restriction for {@code MethodHandle.Lookup}. - * @return the boolean flag to proxy default methods or invoke via {@code MethodHandle}. - * @since 5.3 - */ - boolean proxyDefaultMethods() default false; - - /** - * If errorOnTimeout is true, null won't be returned as a result of a gateway method invocation when a timeout occurs. - * Instead, a {@link org.springframework.integration.MessageTimeoutException} is thrown - * or an error message is published to the error channel. - * @since 6.2 - * @see MessagingGatewaySupport#setErrorOnTimeout(boolean) - */ - boolean errorOnTimeout() default false; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java deleted file mode 100644 index 6d8cd8bdf59..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Payloads.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * This annotation marks a method parameter as being a list of message payloads, for POJO handlers that deal with lists - * of messages (e.g. aggregators and release strategies). - *

- * Example: - * {@code void foo(@Payloads("city.name") List cityName)} - will map the value of the 'name' property of the 'city' - * property of all the payload objects in the input list. - * - * @author Dave Syer - * @since 2.0 - */ -@Target({ElementType.PARAMETER, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Payloads { - - /** - * @return The expression for matching against nested properties of the payloads. - */ - String value() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java deleted file mode 100644 index 92ff287085a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Provides the {@link org.springframework.integration.scheduling.PollerMetadata} options - * for the Messaging annotations for polled endpoints. It is an analogue of the XML - * {@code } element, but provides only simple attributes. If the - * {@link org.springframework.integration.scheduling.PollerMetadata} requires more options - * (e.g. Transactional and other Advices) or {@code initialDelay} etc., the - * {@link org.springframework.integration.scheduling.PollerMetadata} should be configured - * as a generic bean and its bean name can be specified as the {@code value} attribute of - * this annotation. In that case, the other attributes are not allowed. - *

- * Non-reference attributes support Property Placeholder resolutions. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.0 - */ -@Target({}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Poller { - - /** - * @return The {@link org.springframework.integration.scheduling.PollerMetadata} bean - * name. - */ - String value() default ""; - - /** - * @return The {@link org.springframework.scheduling.Trigger} bean name. - */ - String trigger() default ""; - - /** - * @return The {@link org.springframework.core.task.TaskExecutor} bean name. - */ - String taskExecutor() default ""; - - /** - * @return The maximum number of messages to receive for each poll. - * Can be specified as 'property placeholder', e.g. {@code ${poller.maxMessagesPerPoll}}. - * Defaults to -1 (infinity) for polling consumers and 1 for polling inbound channel adapters. - */ - String maxMessagesPerPoll() default ""; - - /** - * @return The fixed delay in milliseconds or a {@link java.time.Duration} compliant string - * to create the {@link org.springframework.scheduling.support.PeriodicTrigger}. - * Can be specified as 'property placeholder', e.g. {@code ${poller.fixedDelay}}. - */ - String fixedDelay() default ""; - - /** - * @return The fixed rate in milliseconds or a {@link java.time.Duration} compliant string - * to create the {@link org.springframework.scheduling.support.PeriodicTrigger} with - * the {@code fixedRate} option. Can be specified as 'property placeholder', e.g. - * {@code ${poller.fixedRate}}. - */ - String fixedRate() default ""; - - /** - * @return The cron expression to create the - * {@link org.springframework.scheduling.support.CronTrigger}. Can be specified as - * 'property placeholder', e.g. {@code ${poller.cron}}. - */ - String cron() default ""; - - /** - * @return The bean name of default error channel - * for the underlying {@code MessagePublishingErrorHandler}. - * @since 4.3.3 - */ - String errorChannel() default ""; - - /** - * Only applies to polling consumers. - * @return the time the poll thread will wait after the trigger for a new message to arrive. - * Defaults to 1000 (1 second). - * For polled inbound channel adapters, whether the polling thread blocks - * is dependent on the message source implementation. - * Can be specified as 'property placeholder', e.g. {@code ${my.poller.receiveTimeout}}. - * @since 5.1 - */ - String receiveTimeout() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Publisher.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Publisher.java deleted file mode 100644 index 4bb9b740ead..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Publisher.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; - -/** - * Annotation to indicate that a method, or all public methods if applied at class-level, - * should publish Messages. - *

- * By default, the Message will be constructed from the return value of the method - * invocation and sent to a channel specified by the {@link #channel()} attribute. - * However, a combination of both @Payload and @Header annotations can be used to further - * manage the message structure. See the reference manual for examples. - *

- * Note: unlike @Gateway, this annotation is used to generate an AOP Advice for an - * existing service and its method implementation. The message sending is a side effect - * of the real method invocation and is invoked after the method returns. - * The advised method(s) are not aware of the messaging interaction. - *

- * The XML equivalent is {@code } - * - * @author Mark Fisher - * @author Jeff Maxwell - * - * @since 2.0 - * - * @see org.springframework.integration.aop.MessagePublishingInterceptor - */ -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Publisher { - - /** - * Alias for the {@link #channel()} attribute. - * @return The name of the Message Channel to which Messages will be published. - * @since 5.0.4 - */ - @AliasFor("channel") - String value() default ""; - - /** - * @return The name of the Message Channel to which Messages will be published. - */ - @AliasFor("value") - String channel() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Reactive.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Reactive.java deleted file mode 100644 index e0f50c47202..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Reactive.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Provides reactive configuration options for the consumer endpoint making - * any input channel as a reactive stream source of data. - * - * @author Artem Bilan - * - * @since 5.5 - */ -@Target({}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Reactive { - - /** - * @return the function bean name to be used in the - * {@link reactor.core.publisher.Flux#transform} on the input channel - * {@link reactor.core.publisher.Flux}. - */ - String value() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ReleaseStrategy.java deleted file mode 100644 index 69989dfec8e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ReleaseStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * Indicates that a method is capable of asserting if a list of messages or - * payload objects is complete. - * - * @author Marius Bogoevici - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Documented -public @interface ReleaseStrategy { - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Role.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Role.java deleted file mode 100644 index 76ca75a0722..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Role.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * Annotate endpoints to assign them to a role. Such endpoints can be started/stopped as - * a group. See {@code SmartLifecycleRoleController}. - * - * @author Gary Russell - * - * @since 4.2 - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Role { - - /** - * @return the role for this endpoint. See {@code SmartLifecycleRoleController}. - */ - String value() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java deleted file mode 100644 index 264c3af662f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of resolving to a channel or channel name - * based on a message, message header(s), or both. - *

- * A method annotated with @Router may accept a parameter of type - * {@link org.springframework.messaging.Message} or of the expected - * Message payload's type. Any type conversion supported by - * {@link org.springframework.beans.SimpleTypeConverter} will be applied to - * the Message payload if necessary. Header values can also be passed as - * Message parameters by using the - * {@link org.springframework.messaging.handler.annotation.Header @Header} parameter annotation. - *

- * Return values from the annotated method may be either a Collection or Array - * whose elements are either - * {@link org.springframework.messaging.MessageChannel channels} or - * Strings. In the latter case, the endpoint hosting this router will attempt - * to resolve each channel name with the Channel Registry or with - * {@link #channelMappings()}, if provided. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Chris Bono - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(Routers.class) -public @interface Router { - - /** - * Specify the channel from which this router will consume messages. - * If the channel does not exist, a {@code DirectChannel} with this name will be - * registered in the application context. - * @return The channel name. - */ - String inputChannel() default ""; - - /** - * Specify the channel to which this router will send messages for which destination - * channels are not resolved and {@link #resolutionRequired()} is false. - * @return The channel name. - */ - String defaultOutputChannel() default ""; - - /** - * The 'key=value' pairs to represent channelMapping entries. - * @return the channelMappings. - * @see org.springframework.integration.router.AbstractMappingMessageRouter#setChannelMapping(String, String) - */ - String[] channelMappings() default {}; - - /** - * Specify a prefix to be added to each channel name prior to resolution. - * @return the prefix. - */ - String prefix() default ""; - - /** - * Specify a suffix to be added to each channel name prior to resolution. - * @return the suffix. - */ - String suffix() default ""; - - /** - * Specify whether channel names must always be successfully resolved - * to existing channel instances. - *

If set to {@code true} (default), a {@link org.springframework.messaging.MessagingException} - * will be raised in case the channel cannot be resolved. Setting this attribute to {@code false}, - * will cause any unresolvable channels to be ignored. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.resolutionRequired}}. - * @return the resolution required flag. - */ - String resolutionRequired() default ""; - - /** - * Specify whether sequence number and size headers should be added to each - * Message. Defaults to {@code false}. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.applySequence}}. - * @return the apply sequence flag. - */ - String applySequence() default ""; - - /** - * If set to {@code true} , failures to send to a message channel will - * be ignored. If set to {@code false} (default), a {@link org.springframework.messaging.MessageDeliveryException} - * will be thrown instead, and if the router resolves more than one channel, - * any subsequent channels will not receive the message. - * Please be aware that when using direct channels (single threaded), - * send-failures can be caused by exceptions thrown by components - * much further down-stream. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.ignoreSendFailures}}. - * @return the ignore send failures flag. - */ - String ignoreSendFailures() default ""; - - /** - * Specify the maximum amount of time in milliseconds to wait when sending a reply - * {@link org.springframework.messaging.Message} to the {@code outputChannel}. - * Defaults to {@code 30} seconds. - * It is applied only if the output channel has some 'sending' limitations, e.g. - * {@link org.springframework.integration.channel.QueueChannel} with - * fixed a 'capacity'. In this case a {@link org.springframework.messaging.MessageDeliveryException} is thrown. - * The 'sendTimeout' is ignored in case of - * {@link org.springframework.integration.channel.AbstractSubscribableChannel} implementations. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendTimeout}}. - * @return The timeout for sending results to the reply target (in milliseconds) - */ - String sendTimeout() default ""; - - /** - * The {@link org.springframework.context.SmartLifecycle} {@code autoStartup} option. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * Defaults to {@code true}. - * @return the auto startup {@code boolean} flag. - */ - String autoStartup() default ""; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Routers.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Routers.java deleted file mode 100644 index 188a7ea706a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Routers.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link Router} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Routers { - - Router[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java deleted file mode 100644 index 07e9e9f9954..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of handling a message or message payload. - *

- * A method annotated with @ServiceActivator may accept a parameter of type - * {@link org.springframework.messaging.Message} or of the expected - * Message payload's type. Any type conversion supported by - * {@link org.springframework.beans.SimpleTypeConverter} will be applied to - * the Message payload if necessary. Header values can also be passed as - * Message parameters by using the - * {@link org.springframework.messaging.handler.annotation.Header @Header} parameter annotation. - *

- * Return values from the annotated method may be of any type. If the return - * value is not a Message, a reply Message will be created with that object - * as its payload. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Yilin Wei - * @author Chris Bono - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(ServiceActivators.class) -public @interface ServiceActivator { - - /** - * Specify the channel from which this service activator will consume messages. - * If the channel does not exist, a {@code DirectChannel} with this name will be - * registered in the application context. - * @return The channel name. - */ - String inputChannel() default ""; - - /** - * Specify the channel to which this service activator will send any replies. - * @return The channel name. - */ - String outputChannel() default ""; - - /** - * Specify whether the service method must return a non-null value. This value is - * {@code false} by default, but if set to {@code true}, a - * {@link org.springframework.integration.handler.ReplyRequiredException} will is thrown when - * the underlying service method (or expression) returns a null value. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.requiresReply}}. - * @return the requires reply flag. - */ - String requiresReply() default ""; - - /** - * Specify a "chain" of {@code Advice} beans that will "wrap" the message handler. - * Only the handler is advised, not the downstream flow. - * @return the advice chain. - */ - String[] adviceChain() default {}; - - /** - * Specify the maximum amount of time in milliseconds to wait when sending a reply - * {@link org.springframework.messaging.Message} to the {@code outputChannel}. - * Defaults to {@code 30} seconds. - * It is applied only if the output channel has some 'sending' limitations, e.g. - * {@link org.springframework.integration.channel.QueueChannel} with - * fixed a 'capacity'. In this case a {@link org.springframework.messaging.MessageDeliveryException} is thrown. - * The 'sendTimeout' is ignored in case of - * {@link org.springframework.integration.channel.AbstractSubscribableChannel} implementations. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendTimeout}}. - * @return The timeout for sending results to the reply target (in milliseconds) - */ - String sendTimeout() default ""; - - /** - * The {@link org.springframework.context.SmartLifecycle} {@code autoStartup} option. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * Defaults to {@code true}. - * @return the auto startup {@code boolean} flag. - */ - String autoStartup() default ""; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * Specify whether the service method is async. - * This value is {@code false} by default. - * @return the async flag. - */ - String async() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivators.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivators.java deleted file mode 100644 index 3ee200965f7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivators.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link ServiceActivator} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ServiceActivators { - - ServiceActivator[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java deleted file mode 100644 index 313033cfe14..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of splitting a single message or message - * payload to produce multiple messages or payloads. - *

- * A method annotated with @Splitter may accept a parameter of type - * {@link org.springframework.messaging.Message} or of the expected - * Message payload's type. Any type conversion supported by - * {@link org.springframework.beans.SimpleTypeConverter} will be applied to - * the Message payload if necessary. Header values can also be passed as - * Message parameters by using the - * {@link org.springframework.messaging.handler.annotation.Header @Header} parameter annotation. - *

- * Return values from the annotated method may be either a Collection or Array - * with elements of any type. If the type is not a Message, each will be used - * as the payload for creating a new Message. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Chris Bono - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(Splitters.class) -public @interface Splitter { - - /** - * Specify the channel from which this splitter will consume messages. - * If the channel does not exist, a {@code DirectChannel} with this name will be - * registered in the application context. - * @return The channel name. - */ - String inputChannel() default ""; - - /** - * Specify the channel to which this splitter will send any replies. - * @return The channel name. - */ - String outputChannel() default ""; - - /** - * Set this flag to {@code false} to prevent adding sequence related headers in this splitter. - * This can be convenient in cases where the set sequence numbers conflict with downstream - * custom aggregations. When {@code true}, existing correlation and sequence related headers - * are pushed onto a stack; downstream components, such as aggregators may pop - * the stack to revert the existing headers after aggregation. - * Defaults to {@code true}. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.applySequence}}. - * @return the apply sequence flag. - */ - String applySequence() default ""; - - /** - * Specify a "chain" of {@code Advice} beans that will "wrap" the message handler. - * Only the handler is advised, not the downstream flow. - * @return the advice chain. - */ - String[] adviceChain() default {}; - - /** - * Specify the maximum amount of time in milliseconds to wait when sending a reply - * {@link org.springframework.messaging.Message} to the {@code outputChannel}. - * Defaults to {@code 30} seconds. - * It is applied only if the output channel has some 'sending' limitations, e.g. - * {@link org.springframework.integration.channel.QueueChannel} with - * fixed a 'capacity'. In this case a {@link org.springframework.messaging.MessageDeliveryException} is thrown. - * The 'sendTimeout' is ignored in case of - * {@link org.springframework.integration.channel.AbstractSubscribableChannel} implementations. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendTimeout}}. - * @return The timeout for sending results to the reply target (in milliseconds) - */ - String sendTimeout() default ""; - - /** - * The {@link org.springframework.context.SmartLifecycle} {@code autoStartup} option. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * Defaults to {@code true}. - * @return the auto startup {@code boolean} flag. - */ - String autoStartup() default ""; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitters.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitters.java deleted file mode 100644 index 12aba2dff89..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitters.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link Splitter} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Splitters { - - Splitter[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java deleted file mode 100644 index 50a65fc1de3..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.messaging.handler.annotation.ValueConstants; - -/** - * Indicates that a method is capable of transforming a message, message header, - * or message payload. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Chris Bono - */ -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(Transformers.class) -public @interface Transformer { - - /** - * Specify the channel from which this transformer will consume messages. - * If the channel does not exist, a {@code DirectChannel} with this name will be - * registered in the application context. - * @return The channel name. - */ - String inputChannel() default ""; - - /** - * Specify the channel to which this transformer will send the transformed message. - * @return The channel name. - */ - String outputChannel() default ""; - - /** - * Specify a "chain" of {@code Advice} objects that will "wrap" the message handler. - * Only the handler is advised, not the downstream flow. - * @return the advice chain. - */ - String[] adviceChain() default {}; - - /** - * Specify the maximum amount of time in milliseconds to wait when sending a reply - * {@link org.springframework.messaging.Message} to the {@code outputChannel}. - * Defaults to {@code 30} seconds. - * It is applied only if the output channel has some 'sending' limitations, e.g. - * {@link org.springframework.integration.channel.QueueChannel} with - * fixed a 'capacity'. In this case a {@link org.springframework.messaging.MessageDeliveryException} is thrown. - * The 'sendTimeout' is ignored in case of - * {@link org.springframework.integration.channel.AbstractSubscribableChannel} implementations. - * Can be specified as 'property placeholder', e.g. {@code ${spring.integration.sendTimeout}}. - * @return The timeout for sending results to the reply target (in milliseconds) - */ - String sendTimeout() default ""; - - /** - * The {@link org.springframework.context.SmartLifecycle} {@code autoStartup} option. - * Can be specified as 'property placeholder', e.g. {@code ${foo.autoStartup}}. - * Defaults to {@code true}. - * @return the auto startup {@code boolean} flag. - */ - String autoStartup() default ""; - - /** - * Specify a {@link org.springframework.context.SmartLifecycle} {@code phase} option. - * Defaults {@code Integer.MAX_VALUE / 2} for {@link org.springframework.integration.endpoint.PollingConsumer} - * and {@code Integer.MIN_VALUE} for {@link org.springframework.integration.endpoint.EventDrivenConsumer}. - * Can be specified as 'property placeholder', e.g. {@code ${foo.phase}}. - * @return the {@code SmartLifecycle} phase. - */ - String phase() default ""; - - /** - * @return the {@link Poller} options for a polled endpoint - * ({@link org.springframework.integration.scheduling.PollerMetadata}). - * Mutually exclusive with {@link #reactive()}. - */ - Poller poller() default @Poller(ValueConstants.DEFAULT_NONE); - - /** - * @return the {@link Reactive} marker for a consumer endpoint. - * Mutually exclusive with {@link #poller()}. - * @since 5.5 - */ - Reactive reactive() default @Reactive(ValueConstants.DEFAULT_NONE); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformers.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformers.java deleted file mode 100644 index 37251a8ff8f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformers.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -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; - -/** - * The repeatable container for {@link Transformer} annotations. - * - * @author Artem Bilan - * - * @since 6.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Transformers { - - Transformer[] value(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/UseSpelInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/UseSpelInvoker.java deleted file mode 100644 index e7bfe0465b7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/UseSpelInvoker.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; - -/** - * Indicates that a POJO handler method ({@code @ServiceActivator, @Transformer, } etc., - * or such methods invoked from XML definitions) should be invoked using SpEL. - *

In versions prior to 5.0, such methods were always invoked using SpEL. In 5.0, the - * framework switched to using - * {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod} instead - * which is generally more efficient than (interpreted) SpEL. - *

There may be some unanticipated corner case where it is necessary to revert to using - * SpEL. Also, for very high performance requirements, you may wish to consider using - * compiled SpEL which is often the fastest solution (when the expression is compilable). - *

Applying this annotation to those methods will cause SpEL to be used for the - * invocation. An optional {@code compilerMode} property (aliased to value) is also provided. - * - * @author Gary Russell - * @since 5.0 - */ -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -public @interface UseSpelInvoker { - - /** - * Specify that the annotated method (or methods in the annotated class) will be - * invoked using SpEL instead of an - * {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod} - * with the specified compilerMode. If left empty, the default runtime compiler - * mode will be used. Must evaluate to a String containing a valid compiler mode. - * @return The compilerMode. - * @see org.springframework.expression.spel.SpelCompilerMode - */ - @AliasFor("compilerMode") - String value() default ""; - - /** - * Specify that the annotated method (or methods in the annotated class) will be - * invoked using SpEL instead of an - * {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod} - * with the specified compilerMode. If left empty, the default runtime compiler - * mode will be used. Must evaluate to a String containing a valid compiler mode. - * @return The compilerMode. - * @see org.springframework.expression.spel.SpelCompilerMode - */ - @AliasFor("value") - String compilerMode() default ""; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java deleted file mode 100644 index 95336d04d75..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides annotations for annotation-based configuration. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.annotation; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java deleted file mode 100644 index f0e34d0a9bd..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.util.CompoundTrigger; -import org.springframework.messaging.Message; -import org.springframework.scheduling.Trigger; -import org.springframework.util.Assert; - -/** - * A {@link MessageSourceMutator} that uses a {@link CompoundTrigger} to adjust - * the poller - when a message is present, the compound trigger's primary trigger is - * used to determine the next poll. When no message is present, the override trigger is - * used. - *

- * The poller advised by this class must be configured to use the same - * {@link CompoundTrigger} instance and must not use a task executor. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -public class CompoundTriggerAdvice implements MessageSourceMutator { - - private final CompoundTrigger compoundTrigger; - - private final Trigger override; - - public CompoundTriggerAdvice(CompoundTrigger compoundTrigger, Trigger overrideTrigger) { - Assert.notNull(compoundTrigger, "'compoundTrigger' cannot be null"); - this.compoundTrigger = compoundTrigger; - this.override = overrideTrigger; - } - - /** - * @param result the received message. - * @param source the message source. - * @return the message or null - */ - @Override - public @Nullable Message afterReceive(@Nullable Message result, MessageSource source) { - if (result == null) { - this.compoundTrigger.setOverride(this.override); - } - else { - this.compoundTrigger.setOverride(null); - } - return result; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java deleted file mode 100644 index 84ff01a8c4b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.core.MessagingTemplate; -import org.springframework.integration.expression.ExpressionEvalMap; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.DefaultMessageBuilderFactory; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.core.DestinationResolver; -import org.springframework.util.Assert; - -/** - * A {@link MethodInterceptor} that publishes Messages to a channel. The - * payload of the published Message can be derived from arguments or any return - * value or exception resulting from the method invocation. That mapping is the - * responsibility of the EL expression provided by the {@link PublisherMetadataSource}. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.0 - */ -public class MessagePublishingInterceptor implements MethodInterceptor, BeanFactoryAware { - - private final MessagingTemplate messagingTemplate = new MessagingTemplate(); - - private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); - - private final PublisherMetadataSource metadataSource; - - @Nullable - private DestinationResolver channelResolver; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - @Nullable - private String defaultChannelName; - - private volatile boolean messageBuilderFactorySet; - - private volatile boolean templateInitialized; - - public MessagePublishingInterceptor(PublisherMetadataSource metadataSource) { - Assert.notNull(metadataSource, "metadataSource must not be null"); - this.metadataSource = metadataSource; - } - - /** - * @param defaultChannelName the default channel name. - * @since 4.0.3 - */ - public void setDefaultChannelName(@Nullable String defaultChannelName) { - this.defaultChannelName = defaultChannelName; - } - - public void setChannelResolver(DestinationResolver channelResolver) { - this.channelResolver = channelResolver; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - protected MessageBuilderFactory getMessageBuilderFactory() { - if (!this.messageBuilderFactorySet) { - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - this.messageBuilderFactorySet = true; - } - return this.messageBuilderFactory; - } - - @Override - public final @Nullable Object invoke(MethodInvocation invocation) throws Throwable { - initMessagingTemplateIfAny(); - StandardEvaluationContext context = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); - Class targetClass = AopUtils.getTargetClass(Objects.requireNonNull(invocation.getThis())); - Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); - @Nullable String[] argumentNames = resolveArgumentNames(method); - context.setVariable(PublisherMetadataSource.METHOD_NAME_VARIABLE_NAME, method.getName()); - if (invocation.getArguments().length > 0 && argumentNames != null) { - Map<@Nullable Object, @Nullable Object> argumentMap = new HashMap<>(); - for (int i = 0; i < argumentNames.length; i++) { - if (invocation.getArguments().length <= i) { - break; - } - Object argValue = invocation.getArguments()[i]; - argumentMap.put(i, argValue); - argumentMap.put(argumentNames[i], argValue); - } - context.setVariable(PublisherMetadataSource.ARGUMENT_MAP_VARIABLE_NAME, argumentMap); - } - try { - Object returnValue = invocation.proceed(); - context.setVariable(PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME, returnValue); - return returnValue; - } - catch (Throwable t) { //NOSONAR - rethrown below - context.setVariable(PublisherMetadataSource.EXCEPTION_VARIABLE_NAME, t); - throw t; - } - finally { - publishMessage(method, context); - } - } - - private void initMessagingTemplateIfAny() { - if (!this.templateInitialized) { - this.messagingTemplate.setBeanFactory(this.beanFactory); - if (this.channelResolver == null) { - this.channelResolver = ChannelResolverUtils.getChannelResolver(this.beanFactory); - } - this.templateInitialized = true; - } - } - - private @Nullable String @Nullable [] resolveArgumentNames(Method method) { - return this.parameterNameDiscoverer.getParameterNames(method); - } - - private void publishMessage(Method method, StandardEvaluationContext context) { - Expression payloadExpression = this.metadataSource.getExpressionForPayload(method); - if (payloadExpression == null) { - payloadExpression = PublisherMetadataSource.RETURN_VALUE_EXPRESSION; - } - Object result = payloadExpression.getValue(context); - if (result != null) { - AbstractIntegrationMessageBuilder builder = (result instanceof Message) - ? getMessageBuilderFactory().fromMessage((Message) result) - : getMessageBuilderFactory().withPayload(result); - Map headers = evaluateHeaders(method, context); - if (headers != null) { - builder.copyHeaders(headers); - } - Message message = builder.build(); - String channelName = this.metadataSource.getChannelName(method); - if (channelName != null) { - this.messagingTemplate.send(channelName, message); - } - else { - String channelNameToUse = this.defaultChannelName; - if (channelNameToUse != null && this.messagingTemplate.getDefaultDestination() == null) { - Assert.state(this.channelResolver != null, "ChannelResolver is required to resolve channel names."); - this.messagingTemplate.setDefaultChannel(this.channelResolver.resolveDestination(channelNameToUse)); - this.defaultChannelName = null; - } - this.messagingTemplate.send(message); - } - } - } - - private @Nullable Map evaluateHeaders(Method method, StandardEvaluationContext context) { - Map headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method); - if (headerExpressionMap != null) { - return ExpressionEvalMap.from(headerExpressionMap) - .usingEvaluationContext(context) - .build(); - } - - return null; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessageSourceMutator.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessageSourceMutator.java deleted file mode 100644 index 233d4eb09e8..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessageSourceMutator.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.core.MessageSource; -import org.springframework.messaging.Message; - -/** - * A {@link ReceiveMessageAdvice} extension that can mutate a {@link MessageSource} before and/or after - * {@link MessageSource#receive()} is called. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0.7 - */ -@FunctionalInterface -public interface MessageSourceMutator extends ReceiveMessageAdvice { - - @Override - default boolean beforeReceive(Object source) { - if (source instanceof MessageSource) { - return beforeReceive((MessageSource) source); - } - else { - throw new IllegalArgumentException( - "The 'MessageSourceMutator' supports only a 'MessageSource' in the before/after hooks: " + source); - } - } - - /** - * Subclasses can decide whether to proceed with this poll. - * @param source the message source. - * @return true to proceed (default). - */ - default boolean beforeReceive(MessageSource source) { - return true; - } - - @Override - @Nullable - default Message afterReceive(@Nullable Message result, Object source) { - if (source instanceof MessageSource) { - return afterReceive(result, (MessageSource) source); - } - else { - throw new IllegalArgumentException( - "The 'MessageSourceMutator' supports only a 'MessageSource' in the before/after hooks: " + source); - } - } - - /** - * Subclasses can take actions based on the result of the poll; e.g. - * adjust the {@code trigger}. The message can also be replaced with a new one. - * @param result the received message. - * @param source the message source. - * @return a message to continue to process the result, null to discard whatever the poll returned. - */ - @Nullable - Message afterReceive(@Nullable Message result, MessageSource source); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java deleted file mode 100644 index 771eaa453e6..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.expression.Expression; -import org.springframework.integration.annotation.Publisher; -import org.springframework.messaging.handler.annotation.Header; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * An {@link PublisherMetadataSource} implementation that retrieves the channel - * name and expression strings from an annotation. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gareth Chapman - * @author Cameron Mayfield - * @author Chengchen Ji - * @author Gary Russell - * - * @since 2.0 - */ -public class MethodAnnotationPublisherMetadataSource implements PublisherMetadataSource { - - private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); - - private final Map channels = new ConcurrentHashMap<>(); - - private final Map payloadExpressions = new ConcurrentHashMap<>(); - - private final Map> headersExpressions = new ConcurrentHashMap<>(); - - private final Set> annotationTypes; - - private volatile String channelAttributeName = "channel"; - - public MethodAnnotationPublisherMetadataSource() { - this(Collections.singleton(Publisher.class)); - } - - public MethodAnnotationPublisherMetadataSource(Set> annotationTypes) { - Assert.notEmpty(annotationTypes, "annotationTypes must not be empty"); - this.annotationTypes = annotationTypes; - } - - public void setChannelAttributeName(String channelAttributeName) { - Assert.hasText(channelAttributeName, "channelAttributeName must not be empty"); - this.channelAttributeName = channelAttributeName; - } - - @Override - public @Nullable String getChannelName(Method method) { - return this.channels.computeIfAbsent(method, - method1 -> { - String channelName = getAnnotationValue(method, this.channelAttributeName); - if (channelName == null) { - channelName = getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName); - } - return StringUtils.hasText(channelName) ? channelName : null; - }); - } - - @Override - public @Nullable Expression getExpressionForPayload(Method method) { - return this.payloadExpressions.computeIfAbsent(method, - method1 -> { - Expression payloadExpression = null; - MergedAnnotation payloadMergedAnnotation = - MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .get(Payload.class); - if (payloadMergedAnnotation.isPresent()) { - String payloadExpressionString = payloadMergedAnnotation.getString("expression"); - if (!StringUtils.hasText(payloadExpressionString)) { - payloadExpression = RETURN_VALUE_EXPRESSION; - } - else { - payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpressionString); - } - } - - Annotation[][] annotationArray = method.getParameterAnnotations(); - for (int i = 0; i < annotationArray.length; i++) { - Annotation[] parameterAnnotations = annotationArray[i]; - payloadMergedAnnotation = MergedAnnotations.from(parameterAnnotations).get(Payload.class); - if (payloadMergedAnnotation.isPresent()) { - Assert.state(payloadExpression == null, - "@Payload can be used at most once on a @Publisher method, " + - "either at method-level or on a single parameter"); - - Assert.state("".equals(payloadMergedAnnotation.getString("expression")), - "@Payload on a parameter for a @Publisher method may not contain an 'expression'"); - - payloadExpression = - EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]"); - } - } - if (payloadExpression == null || - RETURN_VALUE_EXPRESSION.getExpressionString() - .equals(payloadExpression.getExpressionString())) { - Assert.isTrue(!void.class.equals(method.getReturnType()), - "When defining @Publisher on a void-returning method, an explicit payload " + - "expression that does not rely upon a #return value is required."); - } - return payloadExpression; - }); - } - - @Override - public Map getExpressionsForHeaders(Method method) { - return this.headersExpressions.computeIfAbsent(method, - method1 -> { - Map headerExpressions = new HashMap<>(); - @Nullable String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); - Annotation[][] annotationArray = method.getParameterAnnotations(); - for (int i = 0; i < annotationArray.length; i++) { - Annotation[] parameterAnnotations = annotationArray[i]; - MergedAnnotation

headerMergedAnnotation = - MergedAnnotations.from(parameterAnnotations).get(Header.class); - if (headerMergedAnnotation.isPresent()) { - String name = - headerMergedAnnotation - .getString("name"); - if (!StringUtils.hasText(name)) { - if (parameterNames != null) { - name = parameterNames[i]; - } - else { - name = method.getName() + ".arg#" + i; - } - } - headerExpressions.put(name, - EXPRESSION_PARSER.parseExpression('#' + ARGUMENT_MAP_VARIABLE_NAME + '[' + i + ']')); - } - } - return headerExpressions; - }); - } - - @Nullable - private String getAnnotationValue(AnnotatedElement element, String attributeName) { - MergedAnnotations mergedAnnotations = - MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY); - String value = null; - for (Class annotationType : this.annotationTypes) { - MergedAnnotation mergedAnnotation = mergedAnnotations.get(annotationType); - if (mergedAnnotation.isPresent()) { - if (value != null) { - throw new IllegalStateException( - "The [" + element + "] contains more than one publisher annotation"); - } - value = mergedAnnotation.getString(attributeName); - } - } - return value; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java deleted file mode 100644 index 3ffe91970e3..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Map; -import java.util.stream.Collectors; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.util.Assert; -import org.springframework.util.PatternMatchUtils; - -/** - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -public class MethodNameMappingPublisherMetadataSource implements PublisherMetadataSource { - - private final Map payloadExpressionMap; - - private volatile Map> headerExpressionMap = Collections.emptyMap(); - - private volatile Map channelMap = Collections.emptyMap(); - - public MethodNameMappingPublisherMetadataSource(Map payloadExpressionMap) { - Assert.notEmpty(payloadExpressionMap, "payloadExpressionMap must not be empty"); - this.payloadExpressionMap = - payloadExpressionMap.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, - e -> EXPRESSION_PARSER.parseExpression(e.getValue()))); - } - - public void setHeaderExpressionMap(Map> headerExpressionMap) { - this.headerExpressionMap = - headerExpressionMap - .entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, - e -> e.getValue() - .entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, - entry -> EXPRESSION_PARSER.parseExpression(entry.getValue()))))); - } - - public void setChannelMap(Map channelMap) { - this.channelMap = channelMap; - } - - @Override - public @Nullable Expression getExpressionForPayload(Method method) { - for (Map.Entry entry : this.payloadExpressionMap.entrySet()) { - if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { - return entry.getValue(); - } - } - return null; - } - - @Override - public @Nullable Map getExpressionsForHeaders(Method method) { - return this.headerExpressionMap - .entrySet() - .stream() - .filter(e -> PatternMatchUtils.simpleMatch(e.getKey(), method.getName())) - .map(Map.Entry::getValue) - .findFirst() - .orElse(null); - } - - @Override - public @Nullable String getChannelName(Method method) { - for (Map.Entry entry : this.channelMap.entrySet()) { - if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { - return entry.getValue(); - } - } - return null; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java deleted file mode 100644 index 387c87ce85a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.aopalliance.aop.Advice; -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.ClassFilter; -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.Pointcut; -import org.springframework.aop.support.AbstractPointcutAdvisor; -import org.springframework.aop.support.ComposablePointcut; -import org.springframework.aop.support.annotation.AnnotationClassFilter; -import org.springframework.aop.support.annotation.AnnotationMethodMatcher; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.annotation.Publisher; -import org.springframework.util.Assert; - -/** - * An advisor that will apply the {@link MessagePublishingInterceptor} to any - * methods containing the provided annotations. If no annotations are provided, - * the default will be {@link Publisher @Publisher}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Jooyoung Pyoung - * - * @since 2.0 - */ -public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { - - private static final long serialVersionUID = -5387975397101845222L; - - private final transient MessagePublishingInterceptor interceptor; - - private final transient Pointcut pointcut; - - public PublisherAnnotationAdvisor() { - this(Publisher.class); - } - - @SuppressWarnings("varargs") - @SafeVarargs - public PublisherAnnotationAdvisor(Class... publisherAnnotationTypes) { - PublisherMetadataSource metadataSource = - new MethodAnnotationPublisherMetadataSource( - Arrays.stream(publisherAnnotationTypes).collect(Collectors.toSet())); - this.interceptor = new MessagePublishingInterceptor(metadataSource); - this.pointcut = buildPointcut(publisherAnnotationTypes); - } - - /** - * A channel bean name to be used as default for publishing. - * @param defaultChannelName the default channel name. - * @since 4.0.3 - */ - public void setDefaultChannelName(@Nullable String defaultChannelName) { - this.interceptor.setDefaultChannelName(defaultChannelName); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - this.interceptor.setBeanFactory(beanFactory); - } - - @Override - public Advice getAdvice() { - return this.interceptor; - } - - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - - private static Pointcut buildPointcut(Class[] publisherAnnotationTypes) { - ComposablePointcut result = null; - for (Class publisherAnnotationType : publisherAnnotationTypes) { - Pointcut cpc = new MetaAnnotationMatchingPointcut(publisherAnnotationType, true); - Pointcut mpc = new MetaAnnotationMatchingPointcut(null, publisherAnnotationType); - if (result == null) { - result = new ComposablePointcut(cpc).union(mpc); - } - else { - result.union(cpc).union(mpc); - } - } - return Objects.requireNonNull(result); - } - - private static final class MetaAnnotationMatchingPointcut implements Pointcut { - - private final ClassFilter classFilter; - - private final MethodMatcher methodMatcher; - - /** - * Create a new MetaAnnotationMatchingPointcut for the given annotation type. - * @param classAnnotationType the annotation type to look for at the class level - * @param checkInherited whether to explicitly check the superclasses and - * interfaces for the annotation type as well (even if the annotation type - * is not marked as inherited itself) - */ - MetaAnnotationMatchingPointcut(Class classAnnotationType, boolean checkInherited) { - this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited); - this.methodMatcher = MethodMatcher.TRUE; - } - - /** - * Create a new MetaAnnotationMatchingPointcut for the given annotation type. - * @param classAnnotationType the annotation type to look for at the class level - * (can be null) - * @param methodAnnotationType the annotation type to look for at the method level - * (can be null) - */ - MetaAnnotationMatchingPointcut( - @Nullable Class classAnnotationType, @Nullable Class methodAnnotationType) { - - Assert.isTrue((classAnnotationType != null || methodAnnotationType != null), - "Either Class annotation type or Method annotation type needs to be specified (or both)"); - - if (classAnnotationType != null) { - this.classFilter = new AnnotationClassFilter(classAnnotationType); - } - else { - this.classFilter = ClassFilter.TRUE; - } - - if (methodAnnotationType != null) { - this.methodMatcher = new AnnotationMethodMatcher(methodAnnotationType, true); - } - else { - this.methodMatcher = MethodMatcher.TRUE; - } - } - - @Override - public ClassFilter getClassFilter() { - return this.classFilter; - } - - @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java deleted file mode 100644 index 81b071fc1c1..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.util.Assert; - -/** - * Post-processes beans that contain the - * method-level @{@link org.springframework.integration.annotation.Publisher} annotation. - *

- * Only one bean instance of this processor can be declared in the application context, manual - * or automatic by the framework via annotation or XML processing. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Rick Hogge - * - * @since 2.0 - */ -@SuppressWarnings("serial") -public class PublisherAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor - implements BeanNameAware, SmartInitializingSingleton { - - /** - * This value is optional and may be {@code null}, indicating that no default channel - * is configured and message routing must be handled explicitly elsewhere. For example: - * {@link MessagePublishingInterceptor} can accept a null value for this attribute. - * - */ - private @Nullable String defaultChannelName; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - /** - * Set the default channel where Messages should be sent if the annotation - * itself does not provide a channel. - * @param defaultChannelName the publisher interceptor defaultChannel - * @since 4.0.3 - */ - public void setDefaultChannelName(String defaultChannelName) { - this.defaultChannelName = defaultChannelName; - } - - @Override - public void setBeanName(String name) { - Assert.notNull(name, "'name' must not be null"); - this.beanName = name; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - super.setBeanFactory(beanFactory); - PublisherAnnotationAdvisor publisherAnnotationAdvisor = new PublisherAnnotationAdvisor(); - publisherAnnotationAdvisor.setBeanFactory(beanFactory); - publisherAnnotationAdvisor.setDefaultChannelName(this.defaultChannelName); - this.advisor = publisherAnnotationAdvisor; - } - - @Override - public void afterSingletonsInstantiated() { - try { - this.beanFactory.getBean(PublisherAnnotationBeanPostProcessor.class); - } - catch (NoUniqueBeanDefinitionException ex) { - throw new BeanCreationException(this.beanName, - "Only one 'PublisherAnnotationBeanPostProcessor' bean can be defined in the application context." + - " Do not use '@EnablePublisher' (or '') if you declare a" + - " 'PublisherAnnotationBeanPostProcessor' bean definition manually.", ex); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java deleted file mode 100644 index 6d633416a09..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.lang.reflect.Method; -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; - -/** - * Strategy for determining the channel name, payload expression, and header expressions - * for the {@link MessagePublishingInterceptor}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -interface PublisherMetadataSource { - - String METHOD_NAME_VARIABLE_NAME = "method"; - - String ARGUMENT_MAP_VARIABLE_NAME = "args"; - - String RETURN_VALUE_VARIABLE_NAME = "return"; - - String EXCEPTION_VARIABLE_NAME = "exception"; - - ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - - Expression RETURN_VALUE_EXPRESSION = - EXPRESSION_PARSER.parseExpression("#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME); - - /** - * Returns the channel name to which Messages should be published - * for this particular method invocation. - * - * @param method The Method. - * @return The channel name. - */ - @Nullable String getChannelName(Method method); - - /** - * Returns the SpEL expression to be evaluated for creating the Message - * payload. - * @param method the Method. - * @return rhe payload expression. - * @since 5.0.4 - */ - @Nullable Expression getExpressionForPayload(Method method); - - /** - * Returns the map of expression strings to be evaluated for any headers - * that should be set on the published Message. The keys in the Map are - * header names, the values are the expression strings. - * @param method The Method. - * @return The header expressions. - */ - @Nullable Map getExpressionsForHeaders(Method method); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/ReceiveMessageAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/ReceiveMessageAdvice.java deleted file mode 100644 index 242fbdcd6b7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/ReceiveMessageAdvice.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.core.MessageSource; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; - -/** - * An AOP advice to perform hooks before and/or after a {@code receive()} contract is called. - * - * @author Artem Bilan - * - * @since 5.3 - */ -@FunctionalInterface -public interface ReceiveMessageAdvice extends MethodInterceptor { - - /** - * Subclasses can decide whether to {@link MethodInvocation#proceed()} or not. - * @param source the source of the message to receive. - * @return true to proceed (default). - */ - default boolean beforeReceive(Object source) { - return true; - } - - @Override - @Nullable - default Object invoke(MethodInvocation invocation) throws Throwable { - Object target = invocation.getThis(); - if (!(target instanceof MessageSource) && !(target instanceof PollableChannel)) { - return invocation.proceed(); - } - - Message result = null; - if (beforeReceive(target)) { - result = (Message) invocation.proceed(); - } - return afterReceive(result, target); - } - - /** - * Subclasses can take actions based on the result of the {@link MethodInvocation#proceed()}; e.g. - * adjust the {@code trigger}. The message can also be replaced with a new one. - * @param result the received message. - * @param source the source of the message to receive. - * @return a message to continue to process the result, null to discard whatever - * the {@link MethodInvocation#proceed()} returned. - */ - @Nullable - Message afterReceive(@Nullable Message result, Object source); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java deleted file mode 100644 index 5e7d8d49295..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.time.Duration; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.util.DynamicPeriodicTrigger; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * A simple advice that polls at one rate when messages exist and another when - * there are no messages. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.3 - * - * @see DynamicPeriodicTrigger - */ -public class SimpleActiveIdleReceiveMessageAdvice implements ReceiveMessageAdvice { - - private final DynamicPeriodicTrigger trigger; - - private volatile Duration idlePollPeriod; - - private volatile Duration activePollPeriod; - - public SimpleActiveIdleReceiveMessageAdvice(DynamicPeriodicTrigger trigger) { - Assert.notNull(trigger, "'trigger' must not be null"); - this.trigger = trigger; - this.idlePollPeriod = trigger.getDuration(); - this.activePollPeriod = trigger.getDuration(); - } - - /** - * Set the poll period when messages are not returned. Defaults to the - * trigger's period. - * @param idlePollPeriod the period in milliseconds. - */ - public void setIdlePollPeriod(long idlePollPeriod) { - this.idlePollPeriod = Duration.ofMillis(idlePollPeriod); - } - - /** - * Set the poll period when messages are returned. Defaults to the - * trigger's period. - * @param activePollPeriod the period in milliseconds. - */ - public void setActivePollPeriod(long activePollPeriod) { - this.activePollPeriod = Duration.ofMillis(activePollPeriod); - } - - @Override - public @Nullable Message afterReceive(@Nullable Message result, Object source) { - if (result == null) { - this.trigger.setDuration(this.idlePollPeriod); - } - else { - this.trigger.setDuration(this.activePollPeriod); - } - return result; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java deleted file mode 100644 index 0670cf543d3..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aop; - -import java.lang.reflect.Method; -import java.util.Map; -import java.util.stream.Collectors; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; - -/** - * Simple implementation of {@link PublisherMetadataSource} that allows for - * configuration of a single channel name, payload expression, and - * array of header key=value expressions. - * - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -public class SimplePublisherMetadataSource implements PublisherMetadataSource { - - private @Nullable String channelName; - - private @Nullable Expression payloadExpression; - - private @Nullable Map headerExpressions; - - public void setChannelName(String channelName) { - this.channelName = channelName; - } - - @Override - public @Nullable String getChannelName(Method method) { - return this.channelName; - } - - public void setPayloadExpression(String payloadExpression) { - this.payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpression); - } - - @Override - public @Nullable Expression getExpressionForPayload(Method method) { - return this.payloadExpression; - } - - public void setHeaderExpressions(Map headerExpressions) { - this.headerExpressions = - headerExpressions.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, - e -> EXPRESSION_PARSER.parseExpression(e.getValue()))); - } - - @Override - public @Nullable Map getExpressionsForHeaders(Method method) { - return this.headerExpressions; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java deleted file mode 100644 index eb76d2c918e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes to support message publication using AOP. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.aop; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/CoreRuntimeHints.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/CoreRuntimeHints.java deleted file mode 100644 index ba531d7509d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aot/CoreRuntimeHints.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aot; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Properties; -import java.util.UUID; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.AopProxyUtils; -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.SerializationHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.beans.factory.config.BeanExpressionContext; -import org.springframework.context.SmartLifecycle; -import org.springframework.integration.aggregator.MessageGroupProcessor; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.context.IntegrationProperties; -import org.springframework.integration.core.GenericHandler; -import org.springframework.integration.core.GenericSelector; -import org.springframework.integration.core.GenericTransformer; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.core.Pausable; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.gateway.MethodArgsHolder; -import org.springframework.integration.gateway.RequestReplyExchanger; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.DelayHandler; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.json.JsonPathUtils; -import org.springframework.integration.message.AdviceMessage; -import org.springframework.integration.routingslip.ExpressionEvaluatingRoutingSlipRouteStrategy; -import org.springframework.integration.store.MessageGroupMetadata; -import org.springframework.integration.store.MessageHolder; -import org.springframework.integration.store.MessageMetadata; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.MutableMessageHeaders; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.ReactiveMessageHandler; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -/** - * {@link RuntimeHintsRegistrar} for Spring Integration core module. - * - * @author Artem Bilan - * @author Jooyoung Pyoung - * - * @since 6.0 - */ -class CoreRuntimeHints implements RuntimeHintsRegistrar { - - @Override - @SuppressWarnings("removal") - public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - ReflectionHints reflectionHints = hints.reflection(); - Stream.of( - GenericSelector.class, - GenericTransformer.class, - GenericHandler.class, - ReactiveMessageHandler.class, - Function.class, - Supplier.class, - BeanExpressionContext.class, - IntegrationContextUtils.class, - IntegrationProperties.class, - MethodArgsHolder.class, - AbstractReplyProducingMessageHandler.RequestHandler.class, - ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply.class, - Pausable.class) - .forEach(type -> reflectionHints.registerType(type, MemberCategory.INVOKE_PUBLIC_METHODS)); - - if (ClassUtils.isPresent("com.jayway.jsonpath.JsonPath", classLoader)) { - reflectionHints.registerType(JsonPathUtils.class, MemberCategory.INVOKE_PUBLIC_METHODS); - } - - registerJackson2Hints(reflectionHints); - - reflectionHints.registerType( - TypeReference.of("org.springframework.integration.json.JacksonPropertyAccessor$ComparableJsonNode"), - MemberCategory.INVOKE_PUBLIC_METHODS); - - reflectionHints.registerType( - TypeReference.of("org.springframework.integration.json.JacksonPropertyAccessor$ArrayNodeAsList"), - MemberCategory.INVOKE_PUBLIC_METHODS); - - // For #xpath() SpEL function - reflectionHints.registerTypeIfPresent(classLoader, "org.springframework.integration.xml.xpath.XPathUtils", - MemberCategory.INVOKE_PUBLIC_METHODS); - - Stream.of("kotlin.jvm.functions.Function0", "kotlin.jvm.functions.Function1") - .forEach(type -> - reflectionHints.registerTypeIfPresent(classLoader, type, MemberCategory.INVOKE_PUBLIC_METHODS)); - - Stream.of("start", "stop", "isRunning") - .flatMap((name) -> Stream.ofNullable(ReflectionUtils.findMethod(AbstractEndpoint.class, name))) - .forEach(method -> reflectionHints.registerMethod(method, ExecutableMode.INVOKE)); - - hints.resources().registerPattern("META-INF/spring.integration.properties"); - - SerializationHints serializationHints = hints.serialization(); - Stream.of( - String.class, - Number.class, - Long.class, - Date.class, - ArrayList.class, - HashMap.class, - Properties.class, - Hashtable.class, // NOSONAR - Exception.class, - UUID.class, - GenericMessage.class, - ErrorMessage.class, - MessageHeaders.class, - AdviceMessage.class, - MutableMessage.class, - MutableMessageHeaders.class, - MessageGroupMetadata.class, - MessageHolder.class, - MessageMetadata.class, - MessageHistory.class, - MessageHistory.Entry.class, - DelayHandler.DelayedMessageWrapper.class) - .forEach(serializationHints::registerType); - - ProxyHints proxyHints = hints.proxies(); - - registerSpringJdkProxy(proxyHints, PollableChannel.class); - registerSpringJdkProxy(proxyHints, MessageSource.class); - registerSpringJdkProxy(proxyHints, MessageHandler.class); - registerSpringJdkProxy(proxyHints, MessageGroupProcessor.class); - registerSpringJdkProxy(proxyHints, RequestReplyExchanger.class); - registerSpringJdkProxy(proxyHints, AbstractReplyProducingMessageHandler.RequestHandler.class); - registerSpringJdkProxy(proxyHints, IntegrationFlow.class, SmartLifecycle.class); - } - - private static void registerSpringJdkProxy(ProxyHints proxyHints, Class... proxiedInterfaces) { - proxyHints.registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(proxiedInterfaces)); - } - - @Deprecated(since = "7.0", forRemoval = true) - private static void registerJackson2Hints(ReflectionHints reflectionHints) { - reflectionHints.registerType( - TypeReference.of("org.springframework.integration.json.JsonPropertyAccessor$ComparableJsonNode"), - MemberCategory.INVOKE_PUBLIC_METHODS); - - reflectionHints.registerType( - TypeReference.of("org.springframework.integration.json.JsonPropertyAccessor$ArrayNodeAsList"), - MemberCategory.INVOKE_PUBLIC_METHODS); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/GatewayProxyInitializationAotProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/GatewayProxyInitializationAotProcessor.java deleted file mode 100644 index cbde513aee1..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aot/GatewayProxyInitializationAotProcessor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aot; - -import java.util.Arrays; -import java.util.stream.Stream; - -import org.springframework.aop.framework.AopProxyUtils; -import org.springframework.aot.hint.ProxyHints; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.integration.annotation.MessagingGateway; -import org.springframework.integration.gateway.GatewayProxyFactoryBean; - -/** - * A {@link BeanFactoryInitializationAotProcessor} for registering proxy interfaces - * of the {@link GatewayProxyFactoryBean} beans or {@link MessagingGateway} interfaces. - * - * @author Artem Bilan - * - * @since 6.0 - */ -class GatewayProxyInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - Stream> gatewayProxyInterfaces = - Arrays.stream(beanFactory.getBeanDefinitionNames()) - .map((beanName) -> RegisteredBean.of(beanFactory, beanName)) - .filter((bean) -> GatewayProxyFactoryBean.class.isAssignableFrom(bean.getBeanClass())) - .flatMap((bean) -> Stream.ofNullable(bean.getBeanType().getGeneric(0).resolve())); - - return (generationContext, beanFactoryInitializationCode) -> { - ProxyHints proxyHints = generationContext.getRuntimeHints().proxies(); - gatewayProxyInterfaces.forEach((gatewayInterface) -> - proxyHints.registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(gatewayInterface))); - }; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/IntegrationBeanRegistrationExcludeFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/IntegrationBeanRegistrationExcludeFilter.java deleted file mode 100644 index 9d2c4394257..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aot/IntegrationBeanRegistrationExcludeFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.aot; - -import java.util.Arrays; - -import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.integration.config.DefaultConfiguringBeanFactoryPostProcessor; -import org.springframework.integration.config.IntegrationComponentScanRegistrar; -import org.springframework.integration.config.IntegrationConfigurationBeanFactoryPostProcessor; - -/** - * The {@link BeanRegistrationExcludeFilter} to exclude beans not need at runtime anymore. - * Usually {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor} beans - * are excluded since they have contributed their bean definitions code generated during AOT - * build phase. - * - * @author Artem Bilan - * - * @since 6.0 - */ -class IntegrationBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter { - - @Override - public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { - Class beanClass = registeredBean.getBeanClass(); - return Arrays.asList(DefaultConfiguringBeanFactoryPostProcessor.class, - IntegrationConfigurationBeanFactoryPostProcessor.class, - IntegrationComponentScanRegistrar.class) - .contains(beanClass); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aot/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/aot/package-info.java deleted file mode 100644 index c3ae56e0838..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/aot/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes to support Spring AOT. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.aot; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractExecutorChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractExecutorChannel.java deleted file mode 100644 index 40417801b17..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractExecutorChannel.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.Executor; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.dispatcher.AbstractDispatcher; -import org.springframework.integration.support.MessagingExceptionWrapper; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.ExecutorChannelInterceptor; -import org.springframework.messaging.support.MessageHandlingRunnable; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * The {@link AbstractSubscribableChannel} base implementation for those inheritors - * which logic may be based on the {@link Executor}. - *

- * Utilizes common operations for the {@link AbstractDispatcher}. - *

- * Implements the {@link ExecutorChannelInterceptor}s logic when the message handling - * is handed to the {@link Executor#execute(Runnable)}. - * - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * - * @since 4.2 - * - * @see ExecutorChannel - * @see PublishSubscribeChannel - * - */ -public abstract class AbstractExecutorChannel extends AbstractSubscribableChannel - implements ExecutorChannelInterceptorAware { - - protected @Nullable Executor executor; - - @SuppressWarnings("NullAway.Init") - protected AbstractDispatcher dispatcher; - - @Nullable - protected Integer maxSubscribers; - - protected int executorInterceptorsSize; - - public AbstractExecutorChannel(@Nullable Executor executor) { - this.executor = executor; - } - - /** - * Specify the maximum number of subscribers supported by the - * channel's dispatcher. - * @param maxSubscribers The maximum number of subscribers allowed. - */ - public void setMaxSubscribers(int maxSubscribers) { - this.maxSubscribers = maxSubscribers; - this.dispatcher.setMaxSubscribers(maxSubscribers); - } - - @Override - public void setInterceptors(List interceptors) { - super.setInterceptors(interceptors); - for (ChannelInterceptor interceptor : interceptors) { - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - } - - @Override - public void addInterceptor(ChannelInterceptor interceptor) { - super.addInterceptor(interceptor); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - - @Override - public void addInterceptor(int index, ChannelInterceptor interceptor) { - super.addInterceptor(index, interceptor); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - - @Override - public boolean removeInterceptor(ChannelInterceptor interceptor) { - boolean removed = super.removeInterceptor(interceptor); - if (removed && interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize--; - } - return removed; - } - - @Override - public ChannelInterceptor removeInterceptor(int index) { - ChannelInterceptor interceptor = super.removeInterceptor(index); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize--; - } - return interceptor; - } - - @Override - public boolean hasExecutorInterceptors() { - return this.executorInterceptorsSize > 0; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.executor_channel; - } - - protected class MessageHandlingTask implements Runnable { - - private final MessageHandlingRunnable delegate; - - public MessageHandlingTask(MessageHandlingRunnable task) { - this.delegate = task; - } - - @Override - public void run() { - Message message = this.delegate.getMessage(); - MessageHandler messageHandler = this.delegate.getMessageHandler(); - Assert.notNull(messageHandler, "'messageHandler' must not be null"); - Deque interceptorStack = null; - try { - if (AbstractExecutorChannel.this.executorInterceptorsSize > 0) { - interceptorStack = new ArrayDeque<>(); - message = applyBeforeHandle(message, interceptorStack); - if (message == null) { - return; - } - } - messageHandler.handleMessage(message); - if (!CollectionUtils.isEmpty(interceptorStack)) { - triggerAfterMessageHandled(message, null, interceptorStack); - } - } - catch (Exception ex) { - if (!CollectionUtils.isEmpty(interceptorStack)) { - triggerAfterMessageHandled(message, ex, interceptorStack); - } - if (ex instanceof MessagingException) { - throw new MessagingExceptionWrapper(message, (MessagingException) ex); - } - String description = "Failed to handle " + message + " to " + this + " in " + messageHandler; - throw new MessageDeliveryException(message, description, ex); - } - catch (Error ex) { //NOSONAR - ok, we re-throw below - if (!CollectionUtils.isEmpty(interceptorStack)) { - String description = "Failed to handle " + message + " to " + this + " in " + messageHandler; - triggerAfterMessageHandled(message, new MessageDeliveryException(message, description, ex), - interceptorStack); - } - throw ex; - } - } - - @Nullable - private Message applyBeforeHandle(Message message, Deque interceptorStack) { - Message theMessage = message; - for (ChannelInterceptor interceptor : AbstractExecutorChannel.this.interceptors.interceptors) { - if (interceptor instanceof ExecutorChannelInterceptor executorInterceptor) { - theMessage = executorInterceptor.beforeHandle(theMessage, AbstractExecutorChannel.this, - this.delegate.getMessageHandler()); - if (theMessage == null) { - if (isLoggingEnabled()) { - logger.debug(() -> executorInterceptor.getClass().getSimpleName() - + " returned null from beforeHandle, i.e. precluding the send."); - } - triggerAfterMessageHandled(message, null, interceptorStack); - return null; - } - interceptorStack.add(executorInterceptor); - } - } - return theMessage; - } - - private void triggerAfterMessageHandled(Message message, @Nullable Exception ex, - Deque interceptorStack) { - Iterator iterator = interceptorStack.descendingIterator(); - while (iterator.hasNext()) { - ExecutorChannelInterceptor interceptor = iterator.next(); - try { - interceptor.afterMessageHandled(message, AbstractExecutorChannel.this, - this.delegate.getMessageHandler(), ex); - } - catch (Throwable ex2) { //NOSONAR - logger.error(ex2, () -> "Exception from afterMessageHandled in " + interceptor); - } - } - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractMessageChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractMessageChannel.java deleted file mode 100644 index ee4a93092ae..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractMessageChannel.java +++ /dev/null @@ -1,690 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.Lifecycle; -import org.springframework.core.OrderComparator; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.IntegrationPattern; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.MessageDispatchingException; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.management.IntegrationManagedResource; -import org.springframework.integration.support.management.IntegrationManagement; -import org.springframework.integration.support.management.TrackableComponent; -import org.springframework.integration.support.management.metrics.MeterFacade; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.integration.support.management.metrics.SampleFacade; -import org.springframework.integration.support.management.metrics.TimerFacade; -import org.springframework.integration.support.management.observation.DefaultMessageSenderObservationConvention; -import org.springframework.integration.support.management.observation.IntegrationObservation; -import org.springframework.integration.support.management.observation.MessageSenderContext; -import org.springframework.integration.support.management.observation.MessageSenderObservationConvention; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.InterceptableChannel; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Base class for {@link MessageChannel} implementations providing common - * properties such as the channel name. Also provides the common functionality - * for sending and receiving {@link Message Messages} including the invocation - * of any {@link org.springframework.messaging.support.ChannelInterceptor ChannelInterceptors}. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Christian Tzolov - */ -@IntegrationManagedResource -public abstract class AbstractMessageChannel extends IntegrationObjectSupport - implements MessageChannel, TrackableComponent, InterceptableChannel, IntegrationManagement, IntegrationPattern { - - protected final ChannelInterceptorList interceptors = new ChannelInterceptorList(this.logger); // NOSONAR - - private final Comparator orderComparator = new OrderComparator(); - - private final ManagementOverrides managementOverrides = new ManagementOverrides(); - - protected final Set meters = ConcurrentHashMap.newKeySet(); // NOSONAR - - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; - - private @Nullable MessageSenderObservationConvention observationConvention; - - private boolean shouldTrack = false; - - private Class[] datatypes = new Class[0]; - - private @Nullable MessageConverter messageConverter; - - private boolean loggingEnabled = true; - - private @Nullable MetricsCaptor metricsCaptor; - - private @Nullable TimerFacade successTimer; - - private @Nullable TimerFacade failureTimer; - - private volatile @Nullable String fullChannelName; - - private volatile boolean applicationRunning; - - private volatile @Nullable Lifecycle applicationRunningController; - - @Override - public String getComponentType() { - return "channel"; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.message_channel; - } - - @Override - public void setShouldTrack(boolean shouldTrack) { - this.shouldTrack = shouldTrack; - } - - @Override - public void registerMetricsCaptor(MetricsCaptor metricsCaptorToRegister) { - this.metricsCaptor = metricsCaptorToRegister; - } - - @Nullable - protected MetricsCaptor getMetricsCaptor() { - return this.metricsCaptor; - } - - @Override - public boolean isLoggingEnabled() { - return this.loggingEnabled; - } - - @Override - public void setLoggingEnabled(boolean loggingEnabled) { - this.loggingEnabled = loggingEnabled; - this.managementOverrides.loggingConfigured = true; - } - - /** - * Specify the Message payload datatype(s) supported by this channel. If a - * payload type does not match directly, but the 'conversionService' is - * available, then type conversion will be attempted in the order of the - * elements provided in this array. - *

If this property is not set explicitly, any Message payload type will be - * accepted. - * @param datatypes The supported data types. - * @see #setMessageConverter(MessageConverter) - */ - public void setDatatypes(Class... datatypes) { - this.datatypes = Arrays.copyOf(datatypes, datatypes.length); - } - - /** - * Set the list of channel interceptors. This will clear any existing - * interceptors. - * @param interceptors The list of interceptors. - */ - @Override - public void setInterceptors(List interceptors) { - List interceptorsToUse = new ArrayList<>(interceptors); - interceptorsToUse.sort(this.orderComparator); - this.interceptors.set(interceptorsToUse); - } - - /** - * Add a channel interceptor to the end of the list. - * @param interceptor The interceptor. - */ - @Override - public void addInterceptor(ChannelInterceptor interceptor) { - this.interceptors.add(interceptor); - } - - /** - * Add a channel interceptor to the specified index of the list. - * @param index The index to add interceptor. - * @param interceptor The interceptor. - */ - @Override - public void addInterceptor(int index, ChannelInterceptor interceptor) { - this.interceptors.add(index, interceptor); - } - - /** - * Specify the {@link MessageConverter} to use when trying to convert to - * one of this channel's supported datatypes (in order) for a Message whose payload - * does not already match. - *

Note: only the {@link MessageConverter#fromMessage(Message, Class)} - * method is used. If the returned object is not a {@link Message}, the inbound - * headers will be copied; if the returned object is a {@code Message}, it is - * expected that the converter will have fully populated the headers; no - * further action is performed by the channel. If {@code null} is returned, - * conversion to the next datatype (if any) will be attempted. - * Defaults to a - * {@link org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter}. - * @param messageConverter The message converter. - */ - public void setMessageConverter(MessageConverter messageConverter) { - this.messageConverter = messageConverter; - } - - public void setObservationConvention(@Nullable MessageSenderObservationConvention observationConvention) { - this.observationConvention = observationConvention; - } - - /** - * Return a read-only list of the configured interceptors. - */ - @Override - public List getInterceptors() { - return this.interceptors.getInterceptors(); - } - - @Override - public boolean removeInterceptor(ChannelInterceptor interceptor) { - return this.interceptors.remove(interceptor); - } - - @Override - public ChannelInterceptor removeInterceptor(int index) { - return this.interceptors.remove(index); - } - - /** - * Exposes the interceptor list instance for subclasses. - * @return The channel interceptor list. - */ - protected ChannelInterceptorList getIChannelInterceptorList() { - return this.interceptors; - } - - @Override - public ManagementOverrides getOverrides() { - return this.managementOverrides; - } - - @Override - public void registerObservationRegistry(ObservationRegistry observationRegistry) { - Assert.notNull(observationRegistry, "'observationRegistry' must not be null"); - this.observationRegistry = observationRegistry; - } - - @Override - public boolean isObserved() { - return !ObservationRegistry.NOOP.equals(this.observationRegistry); - } - - @Override - protected void onInit() { - super.onInit(); - if (this.messageConverter == null) { - BeanFactory beanFactory = getBeanFactory(); - if (beanFactory != null && - beanFactory.containsBean( - IntegrationContextUtils.INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME)) { - - this.messageConverter = - beanFactory.getBean( - IntegrationContextUtils.INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME, - MessageConverter.class); - } - } - this.fullChannelName = null; - } - - /** - * Returns the fully qualified channel name including the application context - * id, if available. - * - * @return The name. - */ - public String getFullChannelName() { - if (this.fullChannelName == null) { - String contextId = getApplicationContextId(); - String componentName = getComponentName(); - componentName = (StringUtils.hasText(contextId) ? contextId + "." : "") + - (StringUtils.hasText(componentName) ? componentName : "unknown.channel.name"); - this.fullChannelName = componentName; - } - return this.fullChannelName; - } - - /** - * Send a message on this channel. If the channel is at capacity, this - * method will block until either space becomes available or the sending - * thread is interrupted. - * @param message the Message to send - * @return true if the message is sent successfully or - * false if the sending thread is interrupted. - */ - @Override - public boolean send(Message message) { - return send(message, -1); - } - - /** - * Send a message on this channel. If the channel is at capacity, this - * method will block until either the timeout occurs or the sending thread - * is interrupted. If the specified timeout is 0, the method will return - * immediately. If less than zero, it will block indefinitely (see {@link #send(Message)}). - * @param messageArg the Message to send - * @param timeout the timeout in milliseconds - * @return true if the message is sent successfully, - * false if the message cannot be sent within the allotted - * time or the sending thread is interrupted. - */ - @Override - public boolean send(Message messageArg, long timeout) { - Assert.notNull(messageArg, "message must not be null"); - Assert.notNull(messageArg.getPayload(), "message payload must not be null"); - assertApplicationRunning(messageArg); - Message message = messageArg; - if (this.shouldTrack) { - message = MessageHistory.write(message, this, getMessageBuilderFactory()); - } - - if (!ObservationRegistry.NOOP.equals(this.observationRegistry)) { - return sendWithObservation(message, timeout); - } - else if (this.metricsCaptor != null) { - return sendWithMetrics(message, timeout); - } - else { - return sendInternal(message, timeout); - } - } - - protected boolean isApplicationRunning() { - return this.applicationRunning; - } - - private void assertApplicationRunning(Message message) { - if (!this.applicationRunning) { - ApplicationContext applicationContext = getApplicationContext(); - this.applicationRunning = - applicationContext == null || - !applicationContext.containsBean( - IntegrationContextUtils.APPLICATION_RUNNING_CONTROLLER_BEAN_NAME); - - if (!this.applicationRunning) { - if (((ConfigurableApplicationContext) applicationContext).isActive()) { - this.applicationRunningController = - applicationContext.getBean(IntegrationContextUtils.APPLICATION_RUNNING_CONTROLLER_BEAN_NAME, - Lifecycle.class); - this.applicationRunning = this.applicationRunningController.isRunning(); - } - } - } - - if (this.applicationRunning && this.applicationRunningController != null) { - this.applicationRunning = this.applicationRunningController.isRunning(); - } - - if (!this.applicationRunning) { - throw new MessageDispatchingException(message, - """ - The application context is not ready (or stopped) to dispatch messages to '%s' channel. \ - It has to be refreshed or started first. \ - Also, messages must not be emitted from initialization phase, \ - like 'afterPropertiesSet()', '@PostConstruct' or bean definition methods. \ - Consider to use 'SmartLifecycle.start()' instead.""" - .formatted(getComponentName())); - } - } - - private boolean sendWithObservation(Message message, long timeout) { - MutableMessage messageToSend = MutableMessage.of(message); - Observation observation = IntegrationObservation.PRODUCER.observation( - this.observationConvention, - DefaultMessageSenderObservationConvention.INSTANCE, - () -> new MessageSenderContext(messageToSend, getComponentName()), - this.observationRegistry); - Boolean observe = observation.observe(() -> { - Message messageToSendInternal = messageToSend; - if (message instanceof ErrorMessage errorMessage) { - Message originalMessage = errorMessage.getOriginalMessage(); - messageToSendInternal = - (originalMessage != null) ? new ErrorMessage(errorMessage.getPayload(), - messageToSend.getHeaders(), - originalMessage) : new ErrorMessage(errorMessage.getPayload(), - messageToSend.getHeaders()); - } - return sendInternal(messageToSendInternal, timeout); - }); - return Boolean.TRUE.equals(observe); - } - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private boolean sendWithMetrics(Message message, long timeout) { - SampleFacade sample = this.metricsCaptor.start(); - try { - boolean sent = sendInternal(message, timeout); - sample.stop(sendTimer(sent)); - return sent; - } - catch (RuntimeException ex) { - sample.stop(buildSendTimer(false, ex.getClass().getSimpleName())); - throw ex; - } - } - - private boolean sendInternal(Message message, long timeout) { - Deque interceptorStack = null; - boolean sent = false; - ChannelInterceptorList interceptorList = this.interceptors; - Message messageToSend = message; - try { - messageToSend = convertPayloadIfNecessary(messageToSend); - boolean debugEnabled = this.loggingEnabled && this.logger.isDebugEnabled(); - if (debugEnabled) { - logger.debug("preSend on channel '" + this + "', message: " + messageToSend); - } - if (interceptorList.getSize() > 0) { - interceptorStack = new ArrayDeque<>(); - messageToSend = interceptorList.preSend(messageToSend, this, interceptorStack); - if (messageToSend == null) { - return false; - } - } - - sent = doSend(messageToSend, timeout); - - if (debugEnabled) { - logger.debug("postSend (sent=" + sent + ") on channel '" + this + "', message: " + messageToSend); - } - if (interceptorStack != null) { - interceptorList.postSend(messageToSend, this, sent); - interceptorList.afterSendCompletion(messageToSend, this, sent, null, interceptorStack); - } - return sent; - } - catch (Exception ex) { - if (interceptorStack != null) { - interceptorList.afterSendCompletion(messageToSend, this, sent, ex, interceptorStack); - } - throw IntegrationUtils.wrapInDeliveryExceptionIfNecessary(messageToSend, - () -> "failed to send Message to channel '" + getComponentName() + "'", ex); - } - } - - private TimerFacade sendTimer(boolean sent) { - if (sent) { - if (this.successTimer == null) { - this.successTimer = buildSendTimer(true, "none"); - } - return this.successTimer; - } - else { - if (this.failureTimer == null) { - this.failureTimer = buildSendTimer(false, "none"); - } - return this.failureTimer; - } - } - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private TimerFacade buildSendTimer(boolean success, String exception) { - TimerFacade timer = this.metricsCaptor.timerBuilder(SEND_TIMER_NAME) - .tag("type", "channel") - .tag("name", getComponentName() == null ? "unknown" : getComponentName()) - .tag("result", success ? "success" : "failure") - .tag("exception", exception) - .description("Send processing time") - .build(); - this.meters.add(timer); - return timer; - } - - private Message convertPayloadIfNecessary(Message message) { - if (this.datatypes.length > 0) { - // first pass checks if the payload type already matches any of the datatypes - for (Class datatype : this.datatypes) { - if (datatype.isAssignableFrom(message.getPayload().getClass())) { - return message; - } - } - if (this.messageConverter != null) { - // second pass applies conversion if possible, attempting datatypes in order - for (Class datatype : this.datatypes) { - Object converted = this.messageConverter.fromMessage(message, datatype); - if (converted != null) { - if (converted instanceof Message) { - return (Message) converted; - } - else { - return getMessageBuilderFactory() - .withPayload(converted) - .copyHeaders(message.getHeaders()) - .build(); - } - } - } - } - throw new MessageDeliveryException(message, "Channel '" + getComponentName() + - "' expected one of the following data types [" + - StringUtils.arrayToCommaDelimitedString(this.datatypes) + - "], but received [" + message.getPayload().getClass() + "]"); - } - else { - return message; - } - } - - /** - * Subclasses must implement this method. A non-negative timeout indicates - * how long to wait if the channel is at capacity (if the value is 0, it - * must return immediately with or without success). A negative timeout - * value indicates that the method should block until either the message is - * accepted or the blocking thread is interrupted. - * @param message The message. - * @param timeout The timeout. - * @return true if the {@code send} was successful. - */ - protected abstract boolean doSend(Message message, long timeout); - - @Override - public void destroy() { - this.meters.forEach(MeterFacade::remove); - this.meters.clear(); - } - - /** - * A convenience wrapper class for the list of ChannelInterceptors. - */ - protected static class ChannelInterceptorList { - - private final Lock lock = new ReentrantLock(); - - protected final List interceptors = new CopyOnWriteArrayList<>(); // NOSONAR - - private final LogAccessor logger; - - private int size; - - public ChannelInterceptorList(LogAccessor logger) { - this.logger = logger; - } - - public boolean set(List interceptors) { - this.lock.lock(); - try { - this.interceptors.clear(); - this.size = interceptors.size(); - return this.interceptors.addAll(interceptors); - } - finally { - this.lock.unlock(); - } - } - - public int getSize() { - return this.size; - } - - public boolean add(ChannelInterceptor interceptor) { - this.size++; - return this.interceptors.add(interceptor); - } - - public void add(int index, ChannelInterceptor interceptor) { - this.size++; - this.interceptors.add(index, interceptor); - } - - @Nullable - public Message preSend(Message messageArg, MessageChannel channel, - Deque interceptorStack) { - - Message message = messageArg; - if (this.size > 0) { - for (ChannelInterceptor interceptor : this.interceptors) { - Message previous = message; - message = interceptor.preSend(message, channel); - if (message == null) { - this.logger.debug(() -> interceptor.getClass().getSimpleName() - + " returned null from preSend, i.e. precluding the send."); - afterSendCompletion(previous, channel, false, null, interceptorStack); - return null; - } - interceptorStack.add(interceptor); - } - } - return message; - } - - public void postSend(Message message, MessageChannel channel, boolean sent) { - if (this.size > 0) { - for (ChannelInterceptor interceptor : this.interceptors) { - interceptor.postSend(message, channel, sent); - } - } - } - - public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, - @Nullable Exception ex, Deque interceptorStack) { - - for (Iterator iterator = interceptorStack.descendingIterator(); iterator.hasNext(); ) { - ChannelInterceptor interceptor = iterator.next(); - try { - interceptor.afterSendCompletion(message, channel, sent, ex); - } - catch (Exception ex2) { - this.logger.error(ex2, () -> "Exception from afterSendCompletion in " + interceptor); - } - } - } - - public boolean preReceive(MessageChannel channel, Deque interceptorStack) { - if (this.size > 0) { - for (ChannelInterceptor interceptor : this.interceptors) { - if (!interceptor.preReceive(channel)) { - afterReceiveCompletion(null, channel, null, interceptorStack); - return false; - } - interceptorStack.add(interceptor); - } - } - return true; - } - - @Nullable - public Message postReceive(Message messageArg, MessageChannel channel) { - Message message = messageArg; - if (this.size > 0) { - for (ChannelInterceptor interceptor : this.interceptors) { - message = interceptor.postReceive(message, channel); - if (message == null) { - return null; - } - } - } - return message; - } - - public void afterReceiveCompletion(@Nullable Message message, MessageChannel channel, - @Nullable Exception ex, @Nullable Deque interceptorStack) { - - if (interceptorStack != null) { - for (Iterator iter = interceptorStack.descendingIterator(); iter.hasNext(); ) { - ChannelInterceptor interceptor = iter.next(); - try { - interceptor.afterReceiveCompletion(message, channel, ex); - } - catch (Exception ex2) { - this.logger.error(ex2, () -> "Exception from afterReceiveCompletion in " + interceptor); - } - } - } - } - - public List getInterceptors() { - return Collections.unmodifiableList(this.interceptors); - } - - public boolean remove(ChannelInterceptor interceptor) { - if (this.interceptors.remove(interceptor)) { - this.size--; - return true; - } - else { - return false; - } - } - - public ChannelInterceptor remove(int index) { - ChannelInterceptor removed = this.interceptors.remove(index); - this.size--; - return removed; - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractPollableChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractPollableChannel.java deleted file mode 100644 index cf795a032eb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractPollableChannel.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.List; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogMessage; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.support.management.metrics.CounterFacade; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.ExecutorChannelInterceptor; - -/** - * Base class for all pollable channels. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Artem Bilan - * @author Trung Pham - */ -public abstract class AbstractPollableChannel extends AbstractMessageChannel - implements PollableChannel, ExecutorChannelInterceptorAware { - - private int executorInterceptorsSize; - - private @Nullable CounterFacade receiveCounter; - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.pollable_channel; - } - - /** - * Receive the first available message from this channel. If the channel - * contains no messages, this method will block. - * @return the first available message or null if the - * receiving thread is interrupted. - */ - @Override - @Nullable - public Message receive() { - return receive(-1); - } - - /** - * Receive the first available message from this channel. If the channel - * contains no messages, this method will block until the allotted timeout - * elapses. If the specified timeout is 0, the method will return - * immediately. If less than zero, it will block indefinitely (see - * {@link #receive()}). - * @param timeout the timeout in milliseconds - * @return the first available message or null if no message - * is available within the allotted time or the receiving thread is - * interrupted. - */ - @Override // NOSONAR complexity - @Nullable - public Message receive(long timeout) { - ChannelInterceptorList interceptorList = getIChannelInterceptorList(); - Deque interceptorStack = null; - boolean counted = false; - boolean traceEnabled = isLoggingEnabled() && logger.isTraceEnabled(); - try { - if (traceEnabled) { - logger.trace("preReceive on channel '" + this + "'"); - } - if (interceptorList.getSize() > 0) { - interceptorStack = new ArrayDeque<>(); - - if (!interceptorList.preReceive(this, interceptorStack)) { - return null; - } - } - Message message = doReceive(timeout); - if (message == null) { - if (traceEnabled) { - logger.trace("postReceive on channel '" + this + "', message is null"); - } - } - else { - incrementReceiveCounter(); - counted = true; - logger.debug(LogMessage.format("postReceive on channel '%s', message: %s", this, message)); - } - - if (interceptorStack != null && message != null) { - message = interceptorList.postReceive(message, this); - } - interceptorList.afterReceiveCompletion(message, this, null, interceptorStack); - return message; - } - catch (RuntimeException ex) { - if (!counted) { - incrementReceiveErrorCounter(ex); - } - interceptorList.afterReceiveCompletion(null, this, ex, interceptorStack); - throw ex; - } - } - - private void incrementReceiveCounter() { - MetricsCaptor metricsCaptor = getMetricsCaptor(); - if (metricsCaptor != null) { - if (this.receiveCounter == null) { - this.receiveCounter = buildReceiveCounter(metricsCaptor, null); - } - this.receiveCounter.increment(); - } - } - - private void incrementReceiveErrorCounter(Exception ex) { - MetricsCaptor metricsCaptor = getMetricsCaptor(); - if (metricsCaptor != null) { - buildReceiveCounter(metricsCaptor, ex).increment(); - } - } - - private CounterFacade buildReceiveCounter(MetricsCaptor metricsCaptor, @Nullable Exception ex) { - CounterFacade counterFacade = metricsCaptor - .counterBuilder(RECEIVE_COUNTER_NAME) - .tag("name", getComponentName() == null ? "unknown" : getComponentName()) - .tag("type", "channel") - .tag("result", ex == null ? "success" : "failure") - .tag("exception", ex == null ? "none" : ex.getClass().getSimpleName()) - .description("Messages received") - .build(); - this.meters.add(counterFacade); - return counterFacade; - } - - @Override - public void setInterceptors(List interceptors) { - super.setInterceptors(interceptors); - for (ChannelInterceptor interceptor : interceptors) { - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - } - - @Override - public void addInterceptor(ChannelInterceptor interceptor) { - super.addInterceptor(interceptor); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - - @Override - public void addInterceptor(int index, ChannelInterceptor interceptor) { - super.addInterceptor(index, interceptor); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize++; - } - } - - @Override - public boolean removeInterceptor(ChannelInterceptor interceptor) { - boolean removed = super.removeInterceptor(interceptor); - if (removed && interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize--; - } - return removed; - } - - @Override - public ChannelInterceptor removeInterceptor(int index) { - ChannelInterceptor interceptor = super.removeInterceptor(index); - if (interceptor instanceof ExecutorChannelInterceptor) { - this.executorInterceptorsSize--; - } - return interceptor; - } - - @Override - public boolean hasExecutorInterceptors() { - return this.executorInterceptorsSize > 0; - } - - /** - * Subclasses must implement this method. A non-negative timeout indicates - * how long to wait if the channel is empty (if the value is 0, it must - * return immediately with or without success). A negative timeout value - * indicates that the method should block until either a message is - * available or the blocking thread is interrupted. - * @param timeout The timeout. - * @return The message, or null. - */ - @Nullable - protected abstract Message doReceive(long timeout); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractSubscribableChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractSubscribableChannel.java deleted file mode 100644 index fce0be1fb6d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/AbstractSubscribableChannel.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.springframework.integration.MessageDispatchingException; -import org.springframework.integration.dispatcher.MessageDispatcher; -import org.springframework.integration.support.management.SubscribableChannelManagement; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.util.Assert; - -/** - * Base implementation of {@link org.springframework.messaging.MessageChannel} that - * invokes the subscribed {@link MessageHandler handler(s)} by delegating to a - * {@link MessageDispatcher}. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public abstract class AbstractSubscribableChannel extends AbstractMessageChannel - implements SubscribableChannel, SubscribableChannelManagement { - - @Override - public int getSubscriberCount() { - return getRequiredDispatcher().getHandlerCount(); - } - - @Override - public boolean subscribe(MessageHandler handler) { - MessageDispatcher dispatcher = getRequiredDispatcher(); - boolean added = dispatcher.addHandler(handler); - adjustCounterIfNecessary(dispatcher, added ? 1 : 0); - return added; - } - - @Override - public boolean unsubscribe(MessageHandler handle) { - MessageDispatcher dispatcher = getRequiredDispatcher(); - boolean removed = dispatcher.removeHandler(handle); - this.adjustCounterIfNecessary(dispatcher, removed ? -1 : 0); - return removed; - } - - private void adjustCounterIfNecessary(MessageDispatcher dispatcher, int delta) { - if (delta != 0 && logger.isInfoEnabled()) { - logger.info("Channel '" + getFullChannelName() + "' has " + dispatcher.getHandlerCount() - + " subscriber(s)."); - } - } - - @Override - protected boolean doSend(Message message, long timeout) { - try { - return getRequiredDispatcher().dispatch(message); - } - catch (MessageDispatchingException ex) { - String description = ex.getMessage() + " for channel '" + getFullChannelName() + "'."; - throw new MessageDeliveryException(message, description, ex); - } - } - - private MessageDispatcher getRequiredDispatcher() { - MessageDispatcher dispatcher = getDispatcher(); - Assert.state(dispatcher != null, "'dispatcher' must not be null"); - return dispatcher; - } - - protected abstract MessageDispatcher getDispatcher(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/BroadcastCapableChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/BroadcastCapableChannel.java deleted file mode 100644 index 0a0a034392d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/BroadcastCapableChannel.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.springframework.messaging.SubscribableChannel; - -/** - * A {@link SubscribableChannel} variant for implementations with broadcasting capabilities. - * - * @author Artem Bilan - * - * @since 5.3 - */ -public interface BroadcastCapableChannel extends SubscribableChannel { - - /** - * Return a state of this channel in regards of broadcasting capabilities. - * @return the state of this channel in regards of broadcasting capabilities. - */ - default boolean isBroadcast() { - return true; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelPurger.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelPurger.java deleted file mode 100644 index 81fc63440b9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelPurger.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.core.MessageSelector; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * A utility class for purging {@link Message Messages} from one or more - * {@link QueueChannel QueueChannels}. Any message that does not - * match the provided {@link MessageSelector} will be removed from the channel. - * If no {@link MessageSelector} is provided, then all messages will be - * cleared from the channel. - *

- * Note that the {@link #purge()} method operates on a snapshot of the messages - * within a channel at the time that the method is invoked. It is therefore - * possible that new messages will arrive on the channel during the purge - * operation and thus will not be removed. Likewise, messages to be - * purged may have been removed from the channel while the operation is taking - * place. Such messages will not be included in the returned list. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -public class ChannelPurger { - - private final QueueChannel[] channels; - - private final @Nullable MessageSelector selector; - - public ChannelPurger(QueueChannel... channels) { - this(null, channels); - } - - public ChannelPurger(@Nullable MessageSelector selector, QueueChannel... channels) { - Assert.notEmpty(channels, "at least one channel is required"); - if (channels.length == 1) { - Assert.notNull(channels[0], "channel must not be null"); - } - this.selector = selector; - this.channels = Arrays.copyOf(channels, channels.length); - } - - public final List> purge() { - List> purgedMessages = new ArrayList<>(); - for (QueueChannel channel : this.channels) { - List> results = - this.selector == null - ? channel.clear() - : channel.purge(this.selector); - purgedMessages.addAll(results); - } - return purgedMessages; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelUtils.java deleted file mode 100644 index f1f71534dc3..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.util.Assert; -import org.springframework.util.ErrorHandler; - -/** - * Channel utilities. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 5.2 - * - */ -public final class ChannelUtils { - - public static final String MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME = "integrationMessagePublishingErrorHandler"; - - private ChannelUtils() { - } - - /** - * Obtain an {@link ErrorHandler} registered with the - * {@value MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME} bean name. - * By default resolves to the {@link org.springframework.integration.channel.MessagePublishingErrorHandler} - * with the {@value ChannelResolverUtils#CHANNEL_RESOLVER_BEAN_NAME} - * {@link org.springframework.messaging.core.DestinationResolver} bean. - * @param beanFactory BeanFactory for lookup, must not be null. - * @return the instance of {@link ErrorHandler} bean whose name is - * {@value MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME}. - */ - public static ErrorHandler getErrorHandler(BeanFactory beanFactory) { - Assert.notNull(beanFactory, "'beanFactory' must not be null"); - if (!beanFactory.containsBean(MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME)) { - return new MessagePublishingErrorHandler(ChannelResolverUtils.getChannelResolver(beanFactory)); - } - return beanFactory.getBean(MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME, ErrorHandler.class); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultHeaderChannelRegistry.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultHeaderChannelRegistry.java deleted file mode 100644 index 691c880fe9d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultHeaderChannelRegistry.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.time.Instant; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.support.channel.HeaderChannelRegistry; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.messaging.MessageChannel; -import org.springframework.util.Assert; - -/** - * Converts a channel to a name, retaining a reference to the channel keyed by the name. - * Allows a downstream - * {@link org.springframework.integration.support.channel.BeanFactoryChannelResolver} - * to find the channel by name - * in the event that the flow serialized the message at some point. - * Channels are expired after a configurable delay (60 seconds by default). - * The actual average expiry time will be 1.5x the delay. - * - * @author Gary Russell - * @author Artem Bilan - * @author Trung Pham - * @author Christian Tzolov - * - * @since 3.0 - * - */ -public class DefaultHeaderChannelRegistry extends IntegrationObjectSupport - implements HeaderChannelRegistry, ManageableLifecycle, Runnable { - - private static final int DEFAULT_REAPER_DELAY = 60000; - - protected static final AtomicLong id = new AtomicLong(); // NOSONAR - - protected final Map channels = new ConcurrentHashMap<>(); // NOSONAR - - protected final String uuid = UUID.randomUUID() + ":"; // NOSONAR - - private boolean removeOnGet; - - private long reaperDelay; - - private volatile @Nullable ScheduledFuture reaperScheduledFuture; - - private volatile boolean running; - - private volatile boolean explicitlyStopped; - - private final Lock lock = new ReentrantLock(); - - /** - * Construct a registry with the default delay for channel expiry. - */ - public DefaultHeaderChannelRegistry() { - this(DEFAULT_REAPER_DELAY); - } - - /** - * Construct a registry with the provided delay (milliseconds) for - * channel expiry. - * @param reaperDelay the delay in milliseconds. - */ - public DefaultHeaderChannelRegistry(long reaperDelay) { - this.setReaperDelay(reaperDelay); - } - - /** - * Set the reaper delay. - * @param reaperDelay the delay in milliseconds. - */ - public final void setReaperDelay(long reaperDelay) { - Assert.isTrue(reaperDelay > 0, "'reaperDelay' must be > 0"); - this.reaperDelay = reaperDelay; - } - - public final long getReaperDelay() { - return this.reaperDelay; - } - - /** - * Set to true to immediately remove the channel mapping when - * {@link #channelNameToChannel(String)} is invoked. - * @param removeOnGet true to remove immediately, default false. - * @since 4.1 - */ - public void setRemoveOnGet(boolean removeOnGet) { - this.removeOnGet = removeOnGet; - } - - @Override - public final int size() { - return this.channels.size(); - } - - @Override - protected void onInit() { - super.onInit(); - Assert.notNull(getTaskScheduler(), "a task scheduler is required"); - } - - @Override - public void start() { - this.lock.lock(); - try { - if (!this.running) { - Assert.notNull(getTaskScheduler(), "a task scheduler is required"); - this.reaperScheduledFuture = getTaskScheduler() - .schedule(this, Instant.now().plusMillis(this.reaperDelay)); - - this.running = true; - } - } - finally { - this.lock.unlock(); - } - } - - @Override - public void stop() { - this.lock.lock(); - try { - this.running = false; - ScheduledFuture reaperScheduledFutureToCancel = this.reaperScheduledFuture; - if (reaperScheduledFutureToCancel != null) { - reaperScheduledFutureToCancel.cancel(true); - this.reaperScheduledFuture = null; - } - this.explicitlyStopped = true; - } - finally { - this.lock.unlock(); - } - - } - - public void stop(Runnable callback) { - stop(); - callback.run(); - } - - @Override - public boolean isRunning() { - return this.running; - } - - @Override - @Nullable - public Object channelToChannelName(@Nullable Object channel) { - return channelToChannelName(channel, this.reaperDelay); - } - - @Override - @Nullable - public Object channelToChannelName(@Nullable Object channel, long timeToLive) { - if (!this.running && !this.explicitlyStopped && this.getTaskScheduler() != null) { - start(); - } - if (channel instanceof MessageChannel) { - String name = this.uuid + id.incrementAndGet(); - this.channels.put(name, new MessageChannelWrapper((MessageChannel) channel, - System.currentTimeMillis() + timeToLive)); - logger.debug(() -> "Registered " + channel + " as " + name); - return name; - } - else { - return channel; - } - } - - @Override - @Nullable - public MessageChannel channelNameToChannel(@Nullable String name) { - if (name != null) { - MessageChannelWrapper messageChannelWrapper; - if (this.removeOnGet) { - messageChannelWrapper = this.channels.remove(name); - } - else { - messageChannelWrapper = this.channels.get(name); - } - - if (messageChannelWrapper != null) { - MessageChannel channel = messageChannelWrapper.channel(); - logger.debug(() -> "Retrieved " + channel + " with " + name); - return channel; - } - } - return null; - } - - /** - * Cancel the scheduled reap task and run immediately; then reschedule. - */ - @Override - public void runReaper() { - this.lock.lock(); - try { - ScheduledFuture reaperScheduledFutureToCancel = this.reaperScheduledFuture; - if (reaperScheduledFutureToCancel != null) { - reaperScheduledFutureToCancel.cancel(true); - this.reaperScheduledFuture = null; - } - - run(); - } - finally { - this.lock.unlock(); - } - } - - @Override - public void run() { - this.lock.lock(); - try { - logger.trace(() -> "Reaper started; channels size=" + this.channels.size()); - Iterator> iterator = this.channels.entrySet().iterator(); - long now = System.currentTimeMillis(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - if (entry.getValue().expireAt() < now) { - logger.debug(() -> "Expiring " + entry.getKey() + " (" + entry.getValue().channel() + ")"); - iterator.remove(); - } - } - this.reaperScheduledFuture = getTaskScheduler() - .schedule(this, Instant.now().plusMillis(this.reaperDelay)); - - logger.trace(() -> "Reaper completed; channels size=" + this.channels.size()); - } - finally { - this.lock.unlock(); - } - } - - @Override - public String getComponentType() { - return "header-channel-registry"; - } - - protected record MessageChannelWrapper(MessageChannel channel, long expireAt) { - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/DirectChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/DirectChannel.java deleted file mode 100644 index 1a85a352f77..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/DirectChannel.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.function.Predicate; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.dispatcher.LoadBalancingStrategy; -import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy; -import org.springframework.integration.dispatcher.UnicastingDispatcher; - -/** - * A channel that invokes a single subscriber for each sent Message. - * The invocation will occur in the sender's thread. - * - * @author Dave Syer - * @author Mark Fisher - * @author Iwein Fuld - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public class DirectChannel extends AbstractSubscribableChannel { - - private final UnicastingDispatcher dispatcher = new UnicastingDispatcher(); - - private volatile @Nullable Integer maxSubscribers; - - /** - * Create a channel with default {@link RoundRobinLoadBalancingStrategy}. - */ - public DirectChannel() { - this(new RoundRobinLoadBalancingStrategy()); - } - - /** - * Create a DirectChannel with a {@link LoadBalancingStrategy}. - * Can be {@code null} meaning that no balancing is applied; - * every message is always going to be handled by the first subscriber. - * @param loadBalancingStrategy The load balancing strategy implementation. - * @see #setFailover(boolean) - */ - public DirectChannel(@Nullable LoadBalancingStrategy loadBalancingStrategy) { - this.dispatcher.setLoadBalancingStrategy(loadBalancingStrategy); - } - - /** - * Specify whether the channel's dispatcher should have failover enabled. - * By default, it will. Set this value to 'false' to disable it. - * Overrides {@link #setFailoverStrategy(Predicate)} option. - * In other words: or this, or that option has to be set. - * @param failover The failover boolean. - */ - public void setFailover(boolean failover) { - this.dispatcher.setFailover(failover); - } - - /** - * Configure a strategy whether the channel's dispatcher should have failover enabled - * for the exception thrown. - * Overrides {@link #setFailover(boolean)} option. - * In other words: or this, or that option has to be set. - * @param failoverStrategy The failover boolean. - * @since 6.3 - */ - public void setFailoverStrategy(Predicate failoverStrategy) { - this.dispatcher.setFailoverStrategy(failoverStrategy); - } - - /** - * Specify the maximum number of subscribers supported by the - * channel's dispatcher. - * @param maxSubscribers The maximum number of subscribers allowed. - */ - public void setMaxSubscribers(int maxSubscribers) { - this.maxSubscribers = maxSubscribers; - this.dispatcher.setMaxSubscribers(maxSubscribers); - } - - @Override - protected UnicastingDispatcher getDispatcher() { - return this.dispatcher; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.maxSubscribers == null) { - setMaxSubscribers(getIntegrationProperties().getChannelsMaxUnicastSubscribers()); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannel.java deleted file mode 100644 index c992a9ec416..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannel.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.concurrent.Executor; -import java.util.function.Predicate; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.dispatcher.LoadBalancingStrategy; -import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy; -import org.springframework.integration.dispatcher.UnicastingDispatcher; -import org.springframework.integration.util.ErrorHandlingTaskExecutor; -import org.springframework.util.Assert; -import org.springframework.util.ErrorHandler; - -/** - * An implementation of {@link org.springframework.messaging.MessageChannel} - * that delegates to an instance of - * {@link UnicastingDispatcher} which in turn delegates all dispatching - * invocations to an {@link Executor}. - *

- * NOTE: unlike DirectChannel, the ExecutorChannel does not support a - * shared transactional context between sender and handler, because the - * {@link Executor} typically does not block the sender's Thread since it - * uses another Thread for the dispatch. (SyncTaskExecutor is an - * exception but would provide no value for this channel. If synchronous - * dispatching is required, a DirectChannel should be used instead). - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 1.0.3 - */ -public class ExecutorChannel extends AbstractExecutorChannel { - - private final @Nullable LoadBalancingStrategy loadBalancingStrategy; - - private Predicate failoverStrategy = (exception) -> true; - - /** - * Create an ExecutorChannel that delegates to the provided - * {@link Executor} when dispatching Messages. - *

- * The Executor must not be null. - * @param executor The executor. - */ - public ExecutorChannel(Executor executor) { - this(executor, new RoundRobinLoadBalancingStrategy()); - } - - /** - * Create an ExecutorChannel with a {@link LoadBalancingStrategy} that - * delegates to the provided {@link Executor} when dispatching Messages. - *

- * The Executor must not be null. - * @param executor The executor. - * @param loadBalancingStrategy The load balancing strategy implementation. - */ - public ExecutorChannel(Executor executor, @Nullable LoadBalancingStrategy loadBalancingStrategy) { - super(executor); - Assert.notNull(executor, "executor must not be null"); - UnicastingDispatcher unicastingDispatcher = new UnicastingDispatcher(executor); - this.loadBalancingStrategy = loadBalancingStrategy; - if (this.loadBalancingStrategy != null) { - unicastingDispatcher.setLoadBalancingStrategy(this.loadBalancingStrategy); - } - this.dispatcher = unicastingDispatcher; - } - - /** - * Specify whether the channel's dispatcher should have failover enabled. - * By default, it will. Set this value to 'false' to disable it. - * @param failover The failover boolean. - */ - public void setFailover(boolean failover) { - setFailoverStrategy((exception) -> failover); - } - - /** - * Configure a strategy whether the channel's dispatcher should have failover enabled - * for the exception thrown. - * Overrides {@link #setFailover(boolean)} option. - * In other words: or this, or that option has to be set. - * @param failoverStrategy The failover boolean. - * @since 6.3 - */ - public void setFailoverStrategy(Predicate failoverStrategy) { - this.failoverStrategy = failoverStrategy; - getDispatcher().setFailoverStrategy(failoverStrategy); - } - - @Override - protected UnicastingDispatcher getDispatcher() { - return (UnicastingDispatcher) this.dispatcher; - } - - @Override - public final void onInit() { - Assert.state(getDispatcher().getHandlerCount() == 0, "You cannot subscribe() until the channel " - + "bean is fully initialized by the framework. Do not subscribe in a @Bean definition"); - super.onInit(); - - Assert.state(this.executor != null, "Executor must be configured"); - if (!(this.executor instanceof ErrorHandlingTaskExecutor)) { - ErrorHandler errorHandler = ChannelUtils.getErrorHandler(getBeanFactory()); - this.executor = new ErrorHandlingTaskExecutor(this.executor, errorHandler); - } - UnicastingDispatcher unicastingDispatcher = new UnicastingDispatcher(this.executor); - unicastingDispatcher.setFailoverStrategy(this.failoverStrategy); - if (this.maxSubscribers == null) { - this.maxSubscribers = getIntegrationProperties().getChannelsMaxUnicastSubscribers(); - } - unicastingDispatcher.setMaxSubscribers(this.maxSubscribers); - if (this.loadBalancingStrategy != null) { - unicastingDispatcher.setLoadBalancingStrategy(this.loadBalancingStrategy); - } - - unicastingDispatcher.setMessageHandlingTaskDecorator(task -> { - if (ExecutorChannel.this.executorInterceptorsSize > 0) { - return new MessageHandlingTask(task); - } - else { - return task; - } - }); - - this.dispatcher = unicastingDispatcher; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannelInterceptorAware.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannelInterceptorAware.java deleted file mode 100644 index 6795b49bb7b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannelInterceptorAware.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.springframework.messaging.support.InterceptableChannel; - -/** - * The {@link InterceptableChannel} extension for the cases when - * the {@link org.springframework.messaging.support.ExecutorChannelInterceptor}s - * may have reason (e.g. {@link ExecutorChannel} or {@link QueueChannel}) - * and the implementors require to know if they should make the - * {@link org.springframework.messaging.support.ExecutorChannelInterceptor} - * or not. - * - * @author Artem Bilan - * - * @since 4.2 - */ -public interface ExecutorChannelInterceptorAware extends InterceptableChannel { - - boolean hasExecutorInterceptors(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/FixedSubscriberChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/FixedSubscriberChannel.java deleted file mode 100644 index 1ce937c73b5..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/FixedSubscriberChannel.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.integration.support.context.NamedComponent; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.SubscribableChannel; - -/** - * Specialized {@link SubscribableChannel} for a single final subscriber set up during bean instantiation (unlike - * other {@link SubscribableChannel}s where the {@link MessageHandler} is subscribed when the endpoint - * is started). This channel does not support interceptors or data types. - *

- * Note: Stopping ({@link #unsubscribe(MessageHandler)}) the subscribed ({@link MessageHandler}) has no effect. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -public final class FixedSubscriberChannel implements SubscribableChannel, BeanNameAware, NamedComponent { - - private static final Log LOGGER = LogFactory.getLog(FixedSubscriberChannel.class); - - private final MessageHandler handler; - - private @Nullable String beanName; - - public FixedSubscriberChannel() { - throw new IllegalArgumentException("Cannot instantiate a " + this.getClass().getSimpleName() - + " without a MessageHandler."); - } - - public FixedSubscriberChannel(MessageHandler handler) { - this.handler = handler; - } - - @Override - public void setBeanName(String name) { - this.beanName = name; - } - - @Override - public boolean send(Message message) { - return send(message, 0); - } - - @Override - public boolean send(Message message, long timeout) { - try { - this.handler.handleMessage(message); - return true; - } - catch (MessagingException ex) { - if (ex.getFailedMessage() == null) { - throw new MessagingException(message, "Failed to handle Message", ex); - } - else { - throw ex; - } - } - } - - @Override - public boolean subscribe(MessageHandler handler) { - if (!this.handler.equals(handler) && LOGGER.isDebugEnabled()) { - LOGGER.debug(getComponentName() + ": cannot be subscribed to (it has a fixed single subscriber)."); - } - return false; - } - - @Override - public boolean unsubscribe(MessageHandler handler) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(getComponentName() + ": cannot be unsubscribed from (it has a fixed single subscriber)."); - } - return false; - } - - @Override - public String getComponentType() { - return "fixed-subscriber-channel"; - } - - @Override - public String getComponentName() { - if (this.beanName != null) { - return this.beanName; - } - else { - return "Unnamed fixed subscriber channel"; - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/FluxMessageChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/FluxMessageChannel.java deleted file mode 100644 index 3dec966de6a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/FluxMessageChannel.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; - -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import reactor.core.Disposable; -import reactor.core.Disposables; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.util.context.ContextView; - -import org.springframework.context.Lifecycle; -import org.springframework.core.log.LogMessage; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.support.MutableMessageBuilder; -import org.springframework.integration.util.IntegrationReactiveUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * The {@link AbstractMessageChannel} implementation for the - * Reactive Streams {@link Publisher} based on the Project Reactor {@link Flux}. - *

- * This class implements {@link Lifecycle} to control subscriptions to publishers - * attached via {@link #subscribeTo(Publisher)}, when this channel is restarted. - * - * @author Artem Bilan - * @author Gary Russell - * @author Sergei Egorov - * - * @since 5.0 - */ -public class FluxMessageChannel extends AbstractMessageChannel - implements Publisher>, ReactiveStreamsSubscribableChannel, Lifecycle { - - private final Sinks.Many> sink = Sinks.many().multicast().onBackpressureBuffer(1, false); - - private final List>> sourcePublishers = new ArrayList<>(); - - private volatile Disposable.Composite upstreamSubscriptions = Disposables.composite(); - - private volatile boolean active = true; - - @Override - protected boolean doSend(Message message, long timeout) { - Assert.state(this.active && this.sink.currentSubscriberCount() > 0, - () -> "The [" + this + "] doesn't have subscribers to accept messages"); - long remainingTime = 0; - if (timeout > 0) { - remainingTime = timeout; - } - long parkTimeout = 10; // NOSONAR - long parkTimeoutNs = TimeUnit.MILLISECONDS.toNanos(parkTimeout); - while (this.active && !tryEmitMessage(message)) { - remainingTime -= parkTimeout; - if (timeout >= 0 && remainingTime <= 0) { - return false; - } - LockSupport.parkNanos(parkTimeoutNs); - } - return true; - } - - private boolean tryEmitMessage(Message message) { - Message messageToEmit = message; - ContextView contextView = IntegrationReactiveUtils.captureReactorContext(); - if (!contextView.isEmpty()) { - messageToEmit = MutableMessageBuilder.fromMessage(message) - .setHeader(IntegrationMessageHeaderAccessor.REACTOR_CONTEXT, contextView) - .build(); - } - - if (this.active) { - return switch (this.sink.tryEmitNext(messageToEmit)) { - case OK -> true; - case FAIL_NON_SERIALIZED, FAIL_OVERFLOW -> false; - case FAIL_ZERO_SUBSCRIBER -> - throw new IllegalStateException("The [" + this + "] doesn't have subscribers to accept messages"); - case FAIL_TERMINATED, FAIL_CANCELLED -> - throw new IllegalStateException("Cannot emit messages into the cancelled or terminated sink: " - + this.sink); - }; - } - else { - return false; - } - } - - @Override - public void subscribe(Subscriber> subscriber) { - this.sink.asFlux() - .publish(1) - .refCount() - .subscribe(subscriber); - } - - @Override - public void start() { - this.active = true; - this.upstreamSubscriptions = Disposables.composite(); - this.sourcePublishers.forEach(this::doSubscribeTo); - } - - @Override - public void stop() { - this.active = false; - this.upstreamSubscriptions.dispose(); - } - - @Override - public boolean isRunning() { - return this.active; - } - - private void disposeUpstreamSubscription(AtomicReference disposableReference) { - Disposable disposable = disposableReference.get(); - if (disposable != null) { - this.upstreamSubscriptions.remove(disposable); - disposable.dispose(); - } - } - - @Override - public void subscribeTo(Publisher> publisher) { - this.sourcePublishers.add(publisher); - doSubscribeTo(publisher); - } - - private void doSubscribeTo(Publisher> publisher) { - Flux upstreamPublisher = - Flux.from(publisher) - .doOnComplete(() -> this.sourcePublishers.remove(publisher)) - .delaySubscription( - Mono.fromCallable(this.sink::currentSubscriberCount) - .filter((value) -> value > 0) - .repeatWhenEmpty((repeat) -> - this.active ? repeat.delayElements(Duration.ofMillis(100)) : repeat)) - .flatMap((message) -> - Mono.just(message) - .handle((messageToHandle, syncSink) -> sendReactiveMessage(messageToHandle)) - .contextWrite(StaticMessageHeaderAccessor.getReactorContext(message))) - .contextCapture(); - - addPublisherToSubscribe(upstreamPublisher); - } - - private void addPublisherToSubscribe(Flux publisher) { - AtomicReference disposableReference = new AtomicReference<>(); - - Disposable disposable = - publisher - .doOnTerminate(() -> disposeUpstreamSubscription(disposableReference)) - .subscribe(); - - if (!disposable.isDisposed()) { - if (this.upstreamSubscriptions.add(disposable)) { - disposableReference.set(disposable); - } - } - } - - private void sendReactiveMessage(Message message) { - Message messageToSend = message; - // We have just restored Reactor context, so no need in a header anymore. - if (messageToSend.getHeaders().containsKey(IntegrationMessageHeaderAccessor.REACTOR_CONTEXT)) { - messageToSend = - MutableMessageBuilder.fromMessage(message) - .removeHeader(IntegrationMessageHeaderAccessor.REACTOR_CONTEXT) - .build(); - } - try { - if (!send(messageToSend)) { - logger.warn( - new MessageDeliveryException(messageToSend, "Failed to send message to channel '" + this), - "Message was not delivered"); - } - } - catch (Exception ex) { - if (isApplicationRunning()) { - logger.error(ex, LogMessage.format("Error during processing event: %s", messageToSend)); - } - else { - ReflectionUtils.rethrowRuntimeException(ex); - } - } - } - - @Override - public void destroy() { - this.active = false; - this.upstreamSubscriptions.dispose(); - this.sourcePublishers.clear(); - this.sink.emitComplete(Sinks.EmitFailureHandler.busyLooping(Duration.ofSeconds(1))); - super.destroy(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/MessagePublishingErrorHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/MessagePublishingErrorHandler.java deleted file mode 100644 index 0d1dce0a683..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/MessagePublishingErrorHandler.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.core.ErrorMessagePublisher; -import org.springframework.integration.support.ErrorMessageStrategy; -import org.springframework.integration.support.MessagingExceptionWrapper; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.core.DestinationResolver; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.util.Assert; -import org.springframework.util.ErrorHandler; -import org.springframework.util.StringUtils; - -/** - * {@link ErrorHandler} implementation that sends an {@link ErrorMessage} to a - * {@link MessageChannel}. - * - * @author Mark Fisher - * @author Iwein Fuld - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public class MessagePublishingErrorHandler extends ErrorMessagePublisher implements ErrorHandler { - - private static final int DEFAULT_SEND_TIMEOUT = 1000; - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private static final ErrorMessageStrategy DEFAULT_ERROR_MESSAGE_STRATEGY = (ex, attrs) -> { - if (ex instanceof MessagingExceptionWrapper messagingExceptionWrapper) { - return new ErrorMessage(messagingExceptionWrapper.getCause(), - messagingExceptionWrapper.getFailedMessage()); - } - else { - return new ErrorMessage(ex); - } - }; - - @SuppressWarnings("this-escape") - public MessagePublishingErrorHandler() { - setErrorMessageStrategy(DEFAULT_ERROR_MESSAGE_STRATEGY); - setSendTimeout(DEFAULT_SEND_TIMEOUT); - } - - @SuppressWarnings("this-escape") - public MessagePublishingErrorHandler(DestinationResolver channelResolver) { - this(); - setChannelResolver(channelResolver); - } - - public void setDefaultErrorChannel(MessageChannel defaultErrorChannel) { - setChannel(defaultErrorChannel); - } - - /** - * Return the default error channel for this error handler. - * @return the error channel. - * @since 4.3 - */ - @Nullable - public MessageChannel getDefaultErrorChannel() { - return getChannel(); - } - - /** - * Specify the bean name of default error channel for this error handler. - * @param defaultErrorChannelName the bean name of the error channel - * @since 4.3.3 - */ - public void setDefaultErrorChannelName(String defaultErrorChannelName) { - setChannelName(defaultErrorChannelName); - } - - @Override - public final void handleError(Throwable ex) { - MessageChannel errorChannel = resolveErrorChannel(ex); - boolean sent = false; - if (errorChannel != null) { - try { - getMessagingTemplate().send(errorChannel, getErrorMessageStrategy().buildErrorMessage(ex, null)); - sent = true; - } - catch (Exception errorDeliveryError) { - // message will be logged only - if (this.logger.isWarnEnabled()) { - this.logger.warn("Error message was not delivered.", errorDeliveryError); - } - } - } - if (!sent && this.logger.isErrorEnabled()) { - Message failedMessage = - ex instanceof MessagingException - ? ((MessagingException) ex).getFailedMessage() - : null; - - this.logger.error("failure occurred in messaging task" + - (failedMessage != null ? " with message: " + failedMessage : ""), ex); - } - } - - @Nullable - private MessageChannel resolveErrorChannel(Throwable t) { - DestinationResolver channelResolver = getChannelResolver(); - Throwable actualThrowable = t; - if (t instanceof MessagingExceptionWrapper) { - actualThrowable = t.getCause(); - } - Message failedMessage = - actualThrowable instanceof MessagingException - ? ((MessagingException) actualThrowable).getFailedMessage() - : null; - if (getDefaultErrorChannel() == null) { - setChannel(channelResolver.resolveDestination(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)); - } - - if (failedMessage != null && failedMessage.getHeaders().getErrorChannel() != null) { - Object errorChannelHeader = failedMessage.getHeaders().getErrorChannel(); - if (errorChannelHeader instanceof MessageChannel) { - return (MessageChannel) errorChannelHeader; - } - Assert.isInstanceOf(String.class, errorChannelHeader, () -> - "Unsupported error channel header type. Expected MessageChannel or String, but actual type is [" + - errorChannelHeader.getClass() + "]"); - if (StringUtils.hasText((String) errorChannelHeader)) { - return channelResolver.resolveDestination((String) errorChannelHeader); - } - } - - return getDefaultErrorChannel(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java deleted file mode 100644 index 4c560adb87e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; - -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.IntegrationPattern; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.support.management.IntegrationManagedResource; -import org.springframework.integration.support.management.IntegrationManagement; -import org.springframework.integration.support.management.metrics.CounterFacade; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.integration.support.management.metrics.TimerFacade; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; - -/** - * A channel implementation that essentially behaves like "/dev/null". - * All {@link #receive()} calls will return {@code null}, - * and all {@link #send} calls will return {@code true} although no action is performed. - * Unless the payload of a sent message is a {@link Publisher} implementation, in - * which case the {@link Publisher#subscribe(Subscriber)} is called to initiate - * the reactive stream, although the data is discarded by this channel. - * An error thrown from a reactive stream processing (see {@link Subscriber#onError(Throwable)}) - * is logged under the {@code warn} level. - * Note however that the invocations are logged at debug-level. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -@IntegrationManagedResource -public class NullChannel implements PollableChannel, - BeanNameAware, IntegrationManagement, IntegrationPattern { - - private static final LogAccessor LOG = new LogAccessor(NullChannel.class); - - private final ManagementOverrides managementOverrides = new ManagementOverrides(); - - private boolean loggingEnabled = true; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - private @Nullable MetricsCaptor metricsCaptor; - - private @Nullable TimerFacade successTimer; - - private @Nullable CounterFacade receiveCounter; - - @Override - public void setBeanName(String beanName) { - this.beanName = beanName; - } - - @Override - public boolean isLoggingEnabled() { - return this.loggingEnabled; - } - - @Override - public void setLoggingEnabled(boolean loggingEnabled) { - this.loggingEnabled = loggingEnabled; - this.managementOverrides.loggingConfigured = true; - } - - @Override - public String getComponentName() { - return this.beanName; - } - - @Override - public String getComponentType() { - return "null-channel"; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.null_channel; - } - - @Override - public void registerMetricsCaptor(MetricsCaptor registry) { - this.metricsCaptor = registry; - } - - @Override - public ManagementOverrides getOverrides() { - return this.managementOverrides; - } - - @Override - public boolean send(Message message, long timeout) { - return send(message); - } - - @Override - public boolean send(Message message) { - if (this.loggingEnabled) { - LOG.debug(() -> "message sent to null channel: " + message); - } - - Object payload = message.getPayload(); - if (payload instanceof Publisher) { - ((Publisher) payload).subscribe( - new Subscriber() { - - @Override - public void onSubscribe(Subscription subscription) { - subscription.request(Long.MAX_VALUE); - } - - @Override - public void onNext(Object value) { - - } - - @Override - public void onError(Throwable ex) { - LOG.warn(ex, "An error happened in a reactive stream processing"); - } - - @Override - public void onComplete() { - - } - - }); - } - - if (this.metricsCaptor != null) { - sendTimer().record(0, TimeUnit.MILLISECONDS); - } - return true; - } - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private TimerFacade sendTimer() { - if (this.successTimer == null) { - this.successTimer = - this.metricsCaptor.timerBuilder(SEND_TIMER_NAME) - .tag("type", "channel") - .tag("name", getComponentName() == null ? "nullChannel" : getComponentName()) - .tag("result", "success") - .tag("exception", "none") - .description("Subflow process time") - .build(); - } - return this.successTimer; - } - - @Override - public @Nullable Message receive() { - if (this.loggingEnabled) { - LOG.debug("receive called on null channel"); - } - incrementReceiveCounter(); - return null; - } - - @Override - public @Nullable Message receive(long timeout) { - return receive(); - } - - private void incrementReceiveCounter() { - if (this.metricsCaptor != null) { - if (this.receiveCounter == null) { - this.receiveCounter = buildReceiveCounter(); - } - this.receiveCounter.increment(); - } - } - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private CounterFacade buildReceiveCounter() { - return this.metricsCaptor - .counterBuilder(RECEIVE_COUNTER_NAME) - .tag("name", getComponentName() == null ? "unknown" : getComponentName()) - .tag("type", "channel") - .tag("result", "success") - .tag("exception", "none") - .description("Messages received") - .build(); - } - - @Override - public String toString() { - return (this.beanName != null) ? this.beanName : super.toString(); - } - - @Override - public void destroy() { - if (this.successTimer != null) { - this.successTimer.remove(); - } - if (this.receiveCounter != null) { - this.receiveCounter.remove(); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/PartitionedChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/PartitionedChannel.java deleted file mode 100644 index b9ff9aca893..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/PartitionedChannel.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2023-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.Objects; -import java.util.concurrent.ThreadFactory; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.dispatcher.LoadBalancingStrategy; -import org.springframework.integration.dispatcher.PartitionedDispatcher; -import org.springframework.messaging.Message; -import org.springframework.scheduling.concurrent.CustomizableThreadFactory; -import org.springframework.util.Assert; - -/** - * An {@link AbstractExecutorChannel} implementation for partitioned message dispatching. - * Requires a number of partitions where each of them is backed by a dedicated thread. - * The {@code partitionKeyFunction} is used to determine to which partition the message - * has to be dispatched. - * By default, the {@link IntegrationMessageHeaderAccessor#CORRELATION_ID} message header is used - * for partition key. - *

- * The actual dispatching and threading logic is implemented in the {@link PartitionedDispatcher}. - *

- * The default {@link ThreadFactory} is based on the bean name of this channel plus {@code -partition-thread-}. - * Thus, every thread name will reflect a partition it belongs to. - *

- * The rest of the logic is similar to the {@link ExecutorChannel}, which includes: - * - load balancing for subscribers; - * - fail-over and error handling; - * - channel operations intercepting. - * - * @author Artem Bilan - * - * @since 6.1 - * - * @see PartitionedDispatcher - */ -public class PartitionedChannel extends AbstractExecutorChannel { - - @Nullable - private ThreadFactory threadFactory; - - /** - * Instantiate based on a provided number of partitions and function resolving a partition key from - * the {@link IntegrationMessageHeaderAccessor#CORRELATION_ID} message header. - * @param partitionCount the number of partitions in this channel. - * sent to this channel. - */ - public PartitionedChannel(int partitionCount) { - this(partitionCount, (message) -> - Objects.requireNonNull(message.getHeaders().get(IntegrationMessageHeaderAccessor.CORRELATION_ID))); - } - - /** - * Instantiate based on a provided number of partitions and function for partition key against - * the message. - * @param partitionCount the number of partitions in this channel. - * @param partitionKeyFunction the function to resolve a partition key against the message - * sent to this channel. - */ - public PartitionedChannel(int partitionCount, Function, Object> partitionKeyFunction) { - super(null); - this.dispatcher = new PartitionedDispatcher(partitionCount, partitionKeyFunction); - } - - /** - * Set a {@link ThreadFactory} for executors per partitions. - * Propagated down to the {@link PartitionedDispatcher}. - * Defaults to the {@link CustomizableThreadFactory} based on the bean name - * of this channel plus {@code -partition-thread-}. - * @param threadFactory the {@link ThreadFactory} to use. - */ - public void setThreadFactory(ThreadFactory threadFactory) { - Assert.notNull(threadFactory, "'threadFactory' must not be null"); - this.threadFactory = threadFactory; - } - - /** - * Specify whether the channel's dispatcher should have failover enabled. - * By default, it will. Set this value to 'false' to disable it. - * @param failover The failover boolean. - */ - public void setFailover(boolean failover) { - getDispatcher().setFailover(failover); - } - - /** - * Configure a strategy whether the channel's dispatcher should have failover enabled - * for the exception thrown. - * Overrides {@link #setFailover(boolean)} option. - * In other words: or this, or that option has to be set. - * @param failoverStrategy The failover boolean. - * @since 6.3 - */ - public void setFailoverStrategy(Predicate failoverStrategy) { - getDispatcher().setFailoverStrategy(failoverStrategy); - } - - /** - * Provide a {@link LoadBalancingStrategy} for the {@link PartitionedDispatcher}. - * @param loadBalancingStrategy The load balancing strategy implementation. - */ - public void setLoadBalancingStrategy(@Nullable LoadBalancingStrategy loadBalancingStrategy) { - getDispatcher().setLoadBalancingStrategy(loadBalancingStrategy); - } - - @Override - protected PartitionedDispatcher getDispatcher() { - return (PartitionedDispatcher) this.dispatcher; - } - - @Override - protected void onInit() { - super.onInit(); - - if (this.threadFactory == null) { - this.threadFactory = new CustomizableThreadFactory(getComponentName() + "-partition-thread-"); - } - PartitionedDispatcher partitionedDispatcher = getDispatcher(); - partitionedDispatcher.setThreadFactory(this.threadFactory); - - if (this.maxSubscribers == null) { - partitionedDispatcher.setMaxSubscribers(getIntegrationProperties().getChannelsMaxUnicastSubscribers()); - } - - partitionedDispatcher.setErrorHandler(ChannelUtils.getErrorHandler(getBeanFactory())); - - partitionedDispatcher.setMessageHandlingTaskDecorator(task -> { - if (this.executorInterceptorsSize > 0) { - return new MessageHandlingTask(task); - } - else { - return task; - } - }); - - } - - @Override - public void destroy() { - super.destroy(); - getDispatcher().shutdown(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/PriorityChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/PriorityChannel.java deleted file mode 100644 index addfc12f6f6..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/PriorityChannel.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.Comparator; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.atomic.AtomicLong; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.store.MessageGroupQueue; -import org.springframework.integration.store.PriorityCapableChannelMessageStore; -import org.springframework.integration.util.UpperBound; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; - -/** - * A message channel that prioritizes messages based on a {@link Comparator}. - * The default comparator is based upon the message header's 'priority'. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public class PriorityChannel extends QueueChannel { - - /** - * PriorityBlockingQueue#DEFAULT_INITIAL_CAPACITY is private. - */ - private static final int DEFAULT_INITIAL_CAPACITY = 11; - - private final UpperBound upperBound; - - private final AtomicLong sequenceCounter = new AtomicLong(); - - private final boolean useMessageStore; - - /** - * Create a channel with an unbounded queue. Message priority will be - * based on the value of {@link StaticMessageHeaderAccessor#getPriority(Message)}. - */ - public PriorityChannel() { - this(0, null); - } - - /** - * Create a channel with the specified queue capacity. Message priority - * will be based upon the value of {@link StaticMessageHeaderAccessor#getPriority(Message)}. - * @param capacity The queue capacity. - */ - public PriorityChannel(int capacity) { - this(capacity, null); - } - - /** - * Create a channel with an unbounded queue. Message priority will be - * determined by the provided {@link Comparator}. If the comparator - * is null, the priority will be based upon the value of - * {@link StaticMessageHeaderAccessor#getPriority(Message)}. - * @param comparator The comparator. - */ - public PriorityChannel(Comparator> comparator) { - this(0, comparator); - } - - /** - * Create a channel with the specified queue capacity. If the capacity - * is a non-positive value, the queue will be unbounded. Message priority - * will be determined by the provided {@link Comparator}. If the comparator - * is null, the priority will be based upon the value of - * {@link StaticMessageHeaderAccessor#getPriority(Message)}. - * @param capacity The capacity. - * @param comparator The comparator. - */ - public PriorityChannel(int capacity, @Nullable Comparator> comparator) { - super(new PriorityBlockingQueue<>(DEFAULT_INITIAL_CAPACITY, new SequenceFallbackComparator(comparator))); - this.upperBound = new UpperBound(capacity); - this.useMessageStore = false; - } - - /** - * Create a channel based on the provided {@link PriorityCapableChannelMessageStore} - * and group id for message store operations. - * @param messageGroupStore the {@link PriorityCapableChannelMessageStore} to use. - * @param groupId to group message for this channel in the message store. - * @since 5.0 - */ - public PriorityChannel(PriorityCapableChannelMessageStore messageGroupStore, Object groupId) { - this(new MessageGroupQueue(messageGroupStore, groupId)); - } - - /** - * Create a channel based on the provided {@link MessageGroupQueue}. - * @param messageGroupQueue the {@link MessageGroupQueue} to use. - * @since 5.0 - */ - public PriorityChannel(MessageGroupQueue messageGroupQueue) { - super(messageGroupQueue); - this.upperBound = new UpperBound(0); - this.useMessageStore = true; - } - - @Override - public int getRemainingCapacity() { - return this.upperBound.availablePermits(); - } - - @Override - protected boolean doSend(Message message, long timeout) { - if (!this.upperBound.tryAcquire(timeout)) { - return false; - } - if (!this.useMessageStore) { - return super.doSend(new MessageWrapper(message), 0); - } - else { - return super.doSend(message, 0); - } - } - - @Override - protected @Nullable Message doReceive(long timeout) { - Message message = super.doReceive(timeout); - if (message != null) { - if (!this.useMessageStore) { - message = ((MessageWrapper) message).getRootMessage(); - } - this.upperBound.release(); - } - return message; - } - - private static final class SequenceFallbackComparator implements Comparator> { - - private final @Nullable Comparator> targetComparator; - - SequenceFallbackComparator(@Nullable Comparator> targetComparator) { - this.targetComparator = targetComparator; - } - - @Override - public int compare(Message message1, Message message2) { - int compareResult; - if (this.targetComparator != null) { - compareResult = this.targetComparator.compare(message1, message2); - } - else { - Integer priority1 = StaticMessageHeaderAccessor.getPriority(message1); - Integer priority2 = StaticMessageHeaderAccessor.getPriority(message2); - - priority1 = priority1 != null ? priority1 : 0; - priority2 = priority2 != null ? priority2 : 0; - compareResult = priority2.compareTo(priority1); - } - - if (compareResult == 0) { - Long sequence1 = ((MessageWrapper) message1).getSequence(); - Long sequence2 = ((MessageWrapper) message2).getSequence(); - compareResult = sequence1.compareTo(sequence2); - } - return compareResult; - } - - } - - //we need this because of INT-2508 - private final class MessageWrapper implements Message { - - private final Message rootMessage; - - private final long sequence; - - MessageWrapper(Message rootMessage) { - this.rootMessage = rootMessage; - this.sequence = PriorityChannel.this.sequenceCounter.incrementAndGet(); - } - - public Message getRootMessage() { - return this.rootMessage; - } - - @Override - public MessageHeaders getHeaders() { - return this.rootMessage.getHeaders(); - } - - @Override - public Object getPayload() { - return this.rootMessage.getPayload(); - } - - long getSequence() { - return this.sequence; - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/PublishSubscribeChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/PublishSubscribeChannel.java deleted file mode 100644 index b7484ff3dd7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/PublishSubscribeChannel.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.concurrent.Executor; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.integration.dispatcher.BroadcastingDispatcher; -import org.springframework.integration.util.ErrorHandlingTaskExecutor; -import org.springframework.util.Assert; -import org.springframework.util.ErrorHandler; - -/** - * A channel that sends Messages to each of its subscribers. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Trung Pham - */ -public class PublishSubscribeChannel extends AbstractExecutorChannel implements BroadcastCapableChannel { - - private final boolean requireSubscribers; - - private @Nullable ErrorHandler errorHandler; - - private boolean ignoreFailures; - - private boolean applySequence; - - private int minSubscribers; - - /** - * Create a PublishSubscribeChannel that will invoke the handlers in the - * message sender's thread. - */ - public PublishSubscribeChannel() { - this(false); - } - - /** - * Create a PublishSubscribeChannel that will invoke the handlers in the - * message sender's thread considering the provided {@code requireSubscribers} flag. - * @param requireSubscribers if set to true, the sent message is considered as non-dispatched - * and rejected to the caller with the {@code "Dispatcher has no subscribers"}. - * @since 5.4.3 - */ - public PublishSubscribeChannel(boolean requireSubscribers) { - this(null, requireSubscribers); - } - - /** - * Create a PublishSubscribeChannel that will use an {@link Executor} - * to invoke the handlers. If this is null, each invocation will occur in - * the message sender's thread. - * @param executor The executor. - */ - public PublishSubscribeChannel(@Nullable Executor executor) { - this(executor, false); - } - - /** - * Create a PublishSubscribeChannel that will use an {@link Executor} - * to invoke the handlers. If this is null, each invocation will occur in - * the message sender's thread. - * @param executor The executor. - * @param requireSubscribers if set to true, the sent message is considered as non-dispatched - * and rejected to the caller with the {@code "Dispatcher has no subscribers"}. - * @since 5.4.3 - */ - public PublishSubscribeChannel(@Nullable Executor executor, boolean requireSubscribers) { - super(executor); - this.requireSubscribers = requireSubscribers; - this.dispatcher = new BroadcastingDispatcher(executor, requireSubscribers); - } - - @Override - public String getComponentType() { - return "publish-subscribe-channel"; - } - - @Override - public IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.publish_subscribe_channel; - } - - /** - * Provide an {@link ErrorHandler} strategy for handling Exceptions that - * occur downstream from this channel. This will only be applied if - * an Executor has been configured to dispatch the Messages for this - * channel. Otherwise, Exceptions will be thrown directly within the - * sending Thread. If no ErrorHandler is provided, and this channel does - * delegate its dispatching to an Executor, the default strategy is - * a {@link MessagePublishingErrorHandler} that sends error messages to - * the failed request Message's error channel header if available or to - * the default 'errorChannel' otherwise. - * @param errorHandler The error handler. - * @see #PublishSubscribeChannel(Executor) - */ - public void setErrorHandler(ErrorHandler errorHandler) { - this.errorHandler = errorHandler; - } - - /** - * Specify whether failures for one or more of the handlers should be - * ignored. By default, this is false meaning that an Exception - * will be thrown whenever a handler fails. To override this and suppress - * Exceptions, set the value to true. - * @param ignoreFailures true if failures should be ignored. - */ - public void setIgnoreFailures(boolean ignoreFailures) { - this.ignoreFailures = ignoreFailures; - getDispatcher().setIgnoreFailures(ignoreFailures); - } - - /** - * Specify whether to apply the sequence number and size headers to the - * messages prior to invoking the subscribed handlers. By default, this - * value is false meaning that sequence headers will - * not be applied. If planning to use an Aggregator downstream - * with the default correlation and completion strategies, you should set - * this flag to true. - * @param applySequence true if the sequence information should be applied. - */ - public void setApplySequence(boolean applySequence) { - this.applySequence = applySequence; - getDispatcher().setApplySequence(applySequence); - } - - /** - * If at least this number of subscribers receive the message, - * {@link #send(org.springframework.messaging.Message)} - * will return true. Default: 0. - * @param minSubscribers The minimum number of subscribers. - */ - public void setMinSubscribers(int minSubscribers) { - this.minSubscribers = minSubscribers; - getDispatcher().setMinSubscribers(minSubscribers); - } - - /** - * Callback method for initialization. - */ - @Override - public final void onInit() { - super.onInit(); - BeanFactory beanFactory = getBeanFactory(); - BroadcastingDispatcher dispatcherToUse = getDispatcher(); - if (this.executor != null) { - Assert.state(dispatcherToUse.getHandlerCount() == 0, - "When providing an Executor, you cannot subscribe() until the channel " - + "bean is fully initialized by the framework. Do not subscribe in a @Bean definition"); - if (!(this.executor instanceof ErrorHandlingTaskExecutor)) { - if (this.errorHandler == null) { - this.errorHandler = ChannelUtils.getErrorHandler(beanFactory); - } - this.executor = new ErrorHandlingTaskExecutor(this.executor, this.errorHandler); - } - dispatcherToUse = new BroadcastingDispatcher(this.executor, this.requireSubscribers); - dispatcherToUse.setIgnoreFailures(this.ignoreFailures); - dispatcherToUse.setApplySequence(this.applySequence); - dispatcherToUse.setMinSubscribers(this.minSubscribers); - this.dispatcher = dispatcherToUse; - } - else if (this.errorHandler != null) { - this.logger.warn(() -> "The 'errorHandler' is ignored for the '" + getComponentName() + - "' (an 'executor' is not provided) and exceptions will be thrown " + - "directly within the sending Thread"); - } - - if (this.maxSubscribers == null) { - setMaxSubscribers(getIntegrationProperties().getChannelsMaxBroadcastSubscribers()); - } - dispatcherToUse.setBeanFactory(beanFactory); - - dispatcherToUse.setMessageHandlingTaskDecorator(task -> { - if (PublishSubscribeChannel.this.executorInterceptorsSize > 0) { - return new MessageHandlingTask(task); - } - else { - return task; - } - }); - } - - @Override - protected BroadcastingDispatcher getDispatcher() { - return (BroadcastingDispatcher) this.dispatcher; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/QueueChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/QueueChannel.java deleted file mode 100644 index abee763a2c5..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/QueueChannel.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.core.MessageSelector; -import org.springframework.integration.support.management.metrics.GaugeFacade; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Simple implementation of a message channel. Each {@link Message} is placed in - * a {@link BlockingQueue} whose capacity may be specified upon construction. - * The capacity must be a positive integer value. For a zero-capacity version - * based upon a {@link java.util.concurrent.SynchronousQueue}, consider the - * {@link RendezvousChannel}. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public class QueueChannel extends AbstractPollableChannel implements QueueChannelOperations { - - private final Queue> queue; - - protected final Semaphore queueSemaphore = new Semaphore(0); // NOSONAR final - - @Nullable - private GaugeFacade sizeGauge; - - @Nullable - private GaugeFacade remainingCapacityGauge; - - /** - * Create a channel with the specified queue. - * - * @param queue The queue. - */ - public QueueChannel(Queue> queue) { - Assert.notNull(queue, "'queue' must not be null"); - this.queue = queue; - } - - /** - * Create a channel with the specified queue capacity. - * - * @param capacity The capacity. - */ - public QueueChannel(int capacity) { - Assert.isTrue(capacity > 0, "The capacity must be a positive integer. " + - "For a zero-capacity alternative, consider using a 'RendezvousChannel'."); - this.queue = new LinkedBlockingQueue<>(capacity); - } - - /** - * Create a channel with "unbounded" queue capacity. The actual capacity value is - * {@link Integer#MAX_VALUE}. Note that a bounded queue is recommended, since an - * unbounded queue may lead to OutOfMemoryErrors. - */ - public QueueChannel() { - this(new LinkedBlockingQueue<>()); - } - - @Override - public void registerMetricsCaptor(MetricsCaptor metricsCaptor) { - super.registerMetricsCaptor(metricsCaptor); - this.sizeGauge = - metricsCaptor.gaugeBuilder("spring.integration.channel.queue.size", this, - (channel) -> getQueueSize()) - .tag("name", getComponentName() == null ? "unknown" : getComponentName()) - .tag("type", "channel") - .description("The size of the queue channel") - .build(); - - this.remainingCapacityGauge = - metricsCaptor.gaugeBuilder("spring.integration.channel.queue.remaining.capacity", this, - (channel) -> getRemainingCapacity()) - .tag("name", getComponentName() == null ? "unknown" : getComponentName()) - .tag("type", "channel") - .description("The remaining capacity of the queue channel") - .build(); - } - - @Override - protected boolean doSend(Message message, long timeout) { - Assert.notNull(message, "'message' must not be null"); - try { - if (this.queue instanceof BlockingQueue) { - BlockingQueue> blockingQueue = (BlockingQueue>) this.queue; - if (timeout > 0) { - return blockingQueue.offer(message, timeout, TimeUnit.MILLISECONDS); - } - if (timeout == 0) { - return blockingQueue.offer(message); - } - blockingQueue.put(message); - return true; - } - else { - try { - return this.queue.offer(message); - } - finally { - this.queueSemaphore.release(); - } - } - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } - } - - @Override - @Nullable - protected Message doReceive(long timeout) { - try { - if (timeout > 0) { - if (this.queue instanceof BlockingQueue) { - return ((BlockingQueue>) this.queue).poll(timeout, TimeUnit.MILLISECONDS); - } - else { - return pollNonBlockingQueue(timeout); - } - } - if (timeout == 0) { - return this.queue.poll(); - } - - if (this.queue instanceof BlockingQueue) { - return ((BlockingQueue>) this.queue).take(); - } - else { - Message message = this.queue.poll(); - while (message == null) { - this.queueSemaphore.tryAcquire(50, TimeUnit.MILLISECONDS); // NOSONAR ok to ignore result - message = this.queue.poll(); - } - return message; - } - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return null; - } - } - - @Nullable - private Message pollNonBlockingQueue(long timeout) throws InterruptedException { - Message message = this.queue.poll(); - if (message == null) { - long nanos = TimeUnit.MILLISECONDS.toNanos(timeout); - long deadline = System.nanoTime() + nanos; - while (message == null && nanos > 0) { - this.queueSemaphore.tryAcquire(nanos, TimeUnit.NANOSECONDS); // NOSONAR ok to ignore result - message = this.queue.poll(); - if (message == null) { - nanos = deadline - System.nanoTime(); - } - } - } - return message; - } - - @Override - public List> clear() { - List> clearedMessages = new ArrayList<>(); - if (this.queue instanceof BlockingQueue) { - ((BlockingQueue>) this.queue).drainTo(clearedMessages); - } - else { - Message message; - while ((message = this.queue.poll()) != null) { - clearedMessages.add(message); - } - } - return clearedMessages; - } - - @Override - public List> purge(@Nullable MessageSelector selector) { - if (selector == null) { - return this.clear(); - } - List> purgedMessages = new ArrayList<>(); - Object[] array = this.queue.toArray(); - for (Object o : array) { - Message message = (Message) o; - if (!selector.accept(message) && this.queue.remove(message)) { - purgedMessages.add(message); - } - } - return purgedMessages; - } - - @Override - public int getQueueSize() { - return this.queue.size(); - } - - @Override - public int getRemainingCapacity() { - if (this.queue instanceof BlockingQueue) { - return ((BlockingQueue>) this.queue).remainingCapacity(); - } - else { - //Assume that underlying Queue implementation takes care of its size on "offer". - return Integer.MAX_VALUE; - } - } - - @Override - public void destroy() { - super.destroy(); - if (this.sizeGauge != null) { - this.sizeGauge.remove(); - } - if (this.remainingCapacityGauge != null) { - this.remainingCapacityGauge.remove(); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/QueueChannelOperations.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/QueueChannelOperations.java deleted file mode 100644 index 4578b66b054..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/QueueChannelOperations.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.List; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.core.MessageSelector; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.messaging.Message; - -/** - * Operations available on a channel that has queuing semantics. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 3.0 - * - */ -public interface QueueChannelOperations { - - /** - * Remove all {@link Message Messages} from this channel. - * @return The messages that were removed. - */ - List> clear(); - - /** - * Remove any {@link Message Messages} that are not accepted by the provided selector. - * @param selector The message selector. - * @return The list of messages that were purged. - */ - List> purge(@Nullable MessageSelector selector); - - /** - * Obtain the current number of queued {@link Message Messages} in this channel. - * @return The current number of queued {@link Message Messages} in this channel. - */ - @ManagedAttribute(description = "Queue size") - int getQueueSize(); - - /** - * Obtain the remaining capacity of this channel. - * @return The remaining capacity of this channel. - */ - @ManagedAttribute(description = "Queue remaining capacity") - int getRemainingCapacity(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ReactiveStreamsSubscribableChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ReactiveStreamsSubscribableChannel.java deleted file mode 100644 index b17a9f461c8..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/ReactiveStreamsSubscribableChannel.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import org.reactivestreams.Publisher; - -import org.springframework.integration.IntegrationPattern; -import org.springframework.integration.IntegrationPatternType; -import org.springframework.messaging.Message; - -/** - * @author Artem Bilan - * @author Gary Russell - * - * @since 5.0 - */ -public interface ReactiveStreamsSubscribableChannel extends IntegrationPattern { - - void subscribeTo(Publisher> publisher); - - @Override - default IntegrationPatternType getIntegrationPatternType() { - return IntegrationPatternType.reactive_channel; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/RendezvousChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/RendezvousChannel.java deleted file mode 100644 index 1c79ddaee8f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/RendezvousChannel.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel; - -import java.util.concurrent.SynchronousQueue; - -import org.springframework.messaging.Message; - -/** - * A zero-capacity version of {@link QueueChannel} that delegates to a - * {@link SynchronousQueue} internally. This accommodates "handoff" scenarios - * (i.e. blocking while waiting for another party to send or receive). - * - * @author Mark Fisher - */ -public class RendezvousChannel extends QueueChannel { - - public RendezvousChannel() { - super(new SynchronousQueue>()); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorWrapper.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorWrapper.java deleted file mode 100644 index 717a2b61e3e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorWrapper.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel.interceptor; - -import java.util.Arrays; - -import org.springframework.core.Ordered; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.util.Assert; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @since 2.0 - */ -public class GlobalChannelInterceptorWrapper implements Ordered { - - private final ChannelInterceptor channelInterceptor; - - private volatile String[] patterns = {"*"}; // default - - private volatile int order = 0; - - public GlobalChannelInterceptorWrapper(ChannelInterceptor channelInterceptor) { - Assert.notNull(channelInterceptor, "channelInterceptor must not be null"); - this.channelInterceptor = channelInterceptor; - // will set initial order for this interceptor wrapper to be the same as - // the underlying interceptor. Could be overridden with setOrder() method - if (channelInterceptor instanceof Ordered) { - this.order = ((Ordered) channelInterceptor).getOrder(); - } - } - - public ChannelInterceptor getChannelInterceptor() { - return this.channelInterceptor; - } - - public void setOrder(int order) { - this.order = order; - } - - @Override - public final int getOrder() { - return this.order; - } - - public void setPatterns(String[] patterns) { - this.patterns = Arrays.copyOf(patterns, patterns.length); - } - - public String[] getPatterns() { - return this.patterns; // NOSONAR - expose internals - } - - @Override - public String toString() { - return this.channelInterceptor.toString(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/MessageSelectingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/MessageSelectingInterceptor.java deleted file mode 100644 index 69b0d758819..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/MessageSelectingInterceptor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel.interceptor; - -import java.util.Arrays; -import java.util.List; - -import org.springframework.integration.core.MessageSelector; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.support.ChannelInterceptor; - -/** - * A {@link org.springframework.messaging.support.ChannelInterceptor ChannelInterceptor} that - * delegates to a list of {@link MessageSelector MessageSelectors} to decide - * whether a {@link Message} should be accepted on the {@link MessageChannel}. - * - * @author Mark Fisher - * @author Gary Russell - */ -public class MessageSelectingInterceptor implements ChannelInterceptor { - - private final List selectors; - - public MessageSelectingInterceptor(MessageSelector... selectors) { - this.selectors = Arrays.asList(selectors); - } - - @Override - public Message preSend(Message message, MessageChannel channel) { - for (MessageSelector selector : this.selectors) { - if (!selector.accept(message)) { - throw new MessageDeliveryException(message, - "selector '" + selector + "' did not accept message"); - } - } - return message; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java deleted file mode 100644 index b9ff9224dea..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/ThreadStatePropagationChannelInterceptor.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel.interceptor; - -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.support.MessageDecorator; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.ExecutorChannelInterceptor; - -/** - * The {@link ExecutorChannelInterceptor} implementation responsible for - * the {@link Thread} (any?) state propagation from one message flow's thread to another - * through the {@link MessageChannel}s involved in the flow. - *

- * The propagation is done from the {@link #preSend(Message, MessageChannel)} - * implementation using some internal {@link Message} extension which keeps the message - * to send and the state to propagate. - *

- * The propagated state context extraction and population is done from the {@link #postReceive} - * implementation for the {@link org.springframework.messaging.PollableChannel}s, and from - * the {@link #beforeHandle} for the - * {@link org.springframework.integration.channel.AbstractExecutorChannel}s and - * {@link org.springframework.messaging.support.ExecutorSubscribableChannel}s - *

- * Important. Any further interceptor, which modifies the message to send - * (e.g. {@code MessageBuilder.withPayload(...)...build()}), may drop the state to propagate. - * Such kind of interceptors combination should be revised properly. - * In most cases the interceptors reordering is enough to overcome the issue. - * - * @param the propagated state object type. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.2 - */ -public abstract class ThreadStatePropagationChannelInterceptor implements ExecutorChannelInterceptor { - - @Override - public final Message preSend(Message message, MessageChannel channel) { - S threadContext = obtainPropagatingContext(message, channel); - if (threadContext != null) { - if (message instanceof MessageWithThreadState messageWithThreadState) { - messageWithThreadState.stateQueue.add(threadContext); - } - else { - return new MessageWithThreadState(message, threadContext); - } - } - - return message; - } - - @Override - @SuppressWarnings("unchecked") - public final Message postReceive(Message message, MessageChannel channel) { - if (message instanceof MessageWithThreadState messageWithThreadState) { - Object threadContext = messageWithThreadState.stateQueue.poll(); - Message messageToHandle = messageWithThreadState; - if (messageWithThreadState.stateQueue.isEmpty()) { - messageToHandle = messageWithThreadState.message; - } - populatePropagatedContext((S) threadContext, messageToHandle, channel); - return messageToHandle; - } - return message; - } - - @Override - public final Message beforeHandle(Message message, MessageChannel channel, MessageHandler handler) { - return postReceive(message, channel); - } - - protected abstract @Nullable S obtainPropagatingContext(Message message, MessageChannel channel); - - protected abstract void populatePropagatedContext(@Nullable S state, Message message, MessageChannel channel); - - private static final class MessageWithThreadState implements Message, MessageDecorator { - - private final Message message; - - private final Queue stateQueue; - - MessageWithThreadState(Message message, Object state) { - this(message, new LinkedBlockingQueue<>()); - this.stateQueue.add(state); - } - - @SuppressWarnings("unchecked") - private MessageWithThreadState(Message message, Queue stateQueue) { - this.message = (Message) message; - this.stateQueue = new LinkedBlockingQueue<>(stateQueue); - } - - @Override - public Object getPayload() { - return this.message.getPayload(); - } - - @Override - public MessageHeaders getHeaders() { - return this.message.getHeaders(); - } - - @Override - public Message decorateMessage(Message message) { - return new MessageWithThreadState(message, this.stateQueue); - } - - @Override - public String toString() { - return "MessageWithThreadState{" + - "message=" + this.message + - ", state=" + this.stateQueue + - '}'; - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/VetoCapableInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/VetoCapableInterceptor.java deleted file mode 100644 index e6d32c3b91e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/VetoCapableInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel.interceptor; - -import org.springframework.messaging.support.InterceptableChannel; - -/** - * {@link org.springframework.messaging.support.ChannelInterceptor}s implementing this - * interface can veto global interception of a particular channel. - * Could be used, for example, when an interceptor itself writes to an output channel - * (which should not be intercepted with this interceptor). - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -public interface VetoCapableInterceptor { - - /** - * @param beanName The channel name. - * @param channel The channel that is about to be intercepted. - * @return false if the intercept wishes to veto the interception. - */ - boolean shouldIntercept(String beanName, InterceptableChannel channel); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.java deleted file mode 100644 index a7b8f8fd5be..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.channel.interceptor; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.core.MessageSelector; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.InterceptableChannel; -import org.springframework.util.Assert; - -/** - * A {@link ChannelInterceptor} that publishes a copy of the intercepted message - * to a secondary target while still sending the original message to the main channel. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -@ManagedResource -public class WireTap implements ChannelInterceptor, ManageableLifecycle, VetoCapableInterceptor, BeanFactoryAware { - - private static final Log LOGGER = LogFactory.getLog(WireTap.class); - - private final @Nullable MessageSelector selector; - - @SuppressWarnings("NullAway.Init") - private MessageChannel channel; - - private @Nullable String channelName; - - private long timeout = 0; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - @SuppressWarnings("NullAway.Init") - private MessageBuilderFactory messageBuilderFactory; - - private volatile boolean running = true; - - /** - * Create a new wire tap with no {@link MessageSelector}. - * @param channel the MessageChannel to which intercepted messages will be sent - */ - public WireTap(MessageChannel channel) { - this(channel, null); - } - - /** - * Create a new wire tap with the provided {@link MessageSelector}. - * @param channel the channel to which intercepted messages will be sent - * @param selector the selector that must accept a message for it to be - * sent to the intercepting channel - */ - public WireTap(MessageChannel channel, @Nullable MessageSelector selector) { - Assert.notNull(channel, "channel must not be null"); - this.channel = channel; - this.selector = selector; - } - - /** - * Create a new wire tap based on the MessageChannel name and - * with no {@link MessageSelector}. - * @param channelName the name of the target MessageChannel - * to which intercepted messages will be sent - * @since 4.3 - */ - public WireTap(String channelName) { - this(channelName, null); - } - - /** - * Create a new wire tap with the provided {@link MessageSelector}. - * @param channelName the name of the target MessageChannel - * to which intercepted messages will be sent. - * @param selector the selector that must accept a message for it to be - * sent to the intercepting channel - * @since 4.3 - */ - public WireTap(String channelName, @Nullable MessageSelector selector) { - Assert.hasText(channelName, "channelName must not be empty"); - this.channelName = channelName; - this.selector = selector; - } - - /** - * Specify the timeout value for sending to the intercepting target. - * @param timeout the timeout in milliseconds - */ - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (this.beanFactory == null) { - this.beanFactory = beanFactory; - } - } - - /** - * Check whether the wire tap is currently running. - */ - @Override - @ManagedAttribute - public boolean isRunning() { - return this.running; - } - - /** - * Restart the wire tap if it has been stopped. It is running by default. - */ - @Override - @ManagedOperation - public void start() { - this.running = true; - } - - /** - * Stop the wire tap. To restart, invoke {@link #start()}. - */ - @Override - @ManagedOperation - public void stop() { - this.running = false; - } - - /** - * Intercept the Message and, if accepted by the {@link MessageSelector}, - * send it to the secondary target. If this wire tap's {@link MessageSelector} is - * null, it will accept all messages. - */ - @Override - public Message preSend(Message message, MessageChannel channel) { - MessageChannel wireTapChannel = getChannel(); - if (wireTapChannel.equals(channel)) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("WireTap is refusing to intercept its own channel '" + wireTapChannel + "'"); - } - return message; - } - if (this.running && (this.selector == null || this.selector.accept(message))) { - Message messageToSend = message; - if (message.getHeaders().containsKey(MessageHistory.HEADER_NAME)) { - messageToSend = - getMessageBuilderFactory() - .fromMessage(message) - .cloneMessageHistoryIfAny() - .build(); - } - boolean sent = - (this.timeout >= 0) - ? wireTapChannel.send(messageToSend, this.timeout) - : wireTapChannel.send(messageToSend); - - if (!sent && LOGGER.isWarnEnabled()) { - LOGGER.warn("failed to send message to WireTap channel '" + wireTapChannel + "'"); - } - } - return message; - } - - @Override - public boolean shouldIntercept(String beanName, InterceptableChannel channel) { - return !getChannel().equals(channel); - } - - private MessageChannel getChannel() { - String channelNameToUse = this.channelName; - if (channelNameToUse != null) { - this.channel = - ChannelResolverUtils.getChannelResolver(this.beanFactory) - .resolveDestination(channelNameToUse); - this.channelName = null; - } - return this.channel; - } - - private MessageBuilderFactory getMessageBuilderFactory() { - if (this.messageBuilderFactory == null) { - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - } - return this.messageBuilderFactory; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/package-info.java deleted file mode 100644 index f531cba6abb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to channel interception. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.channel.interceptor; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/package-info.java deleted file mode 100644 index 5f5b24d989c..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes representing various channel types. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.channel; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/Codec.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/Codec.java deleted file mode 100644 index 4564b759d1a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/Codec.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Interface for classes that perform both encode (serialize) and decode (deserialize) on multiple classes. - * - * @author David Turanski - * @since 4.2 - */ -public interface Codec { - - /** - * Encode (encode) an object to an OutputStream. - * @param object the object to encode - * @param outputStream the OutputStream - * @throws IOException if the operation fails - */ - void encode(Object object, OutputStream outputStream) throws IOException; - - /** - * Encode an object to a byte array. - * @param object the object to encode - * @return the bytes - * @throws IOException if the operation fails - */ - byte[] encode(Object object) throws IOException; - - /** - * Decode an object of a given type. - * @param inputStream the input stream containing the encoded object - * @param type the object's class - * @param the object's type - * @return the object - * @throws IOException if the operation fails - */ - T decode(InputStream inputStream, Class type) throws IOException; - - /** - * Decode an object of a given type. - * @param bytes the byte array containing the encoded object - * @param type the object's class - * @param the object's type - * @return the object - * @throws IOException if the operation fails - */ - T decode(byte[] bytes, Class type) throws IOException; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/CodecMessageConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/CodecMessageConverter.java deleted file mode 100644 index 7de15aaadae..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/CodecMessageConverter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec; - -import java.io.IOException; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.Assert; - -/** - * A {@link MessageConverter} that delegates to a {@link Codec} to convert. - * - * @author Gary Russell - * - * @since 4.2 - * - */ -public class CodecMessageConverter extends IntegrationObjectSupport implements MessageConverter { - - private final Codec codec; - - private final Class messageClass; - - public CodecMessageConverter(Codec codec) { - this.codec = codec; - this.messageClass = GenericMessage.class; - } - - @Override - public Object fromMessage(Message message, Class targetClass) { - try { - return this.codec.encode(message); - } - catch (IOException e) { - throw new MessagingException(message, "Failed to encode Message", e); - } - } - - @Override - public Message toMessage(Object payload, @Nullable MessageHeaders headers) { - Assert.isInstanceOf(byte[].class, payload); - try { - Message decoded = (Message) this.codec.decode((byte[]) payload, this.messageClass); - if (headers != null) { - AbstractIntegrationMessageBuilder builder = getMessageBuilderFactory().fromMessage(decoded); - builder.copyHeaders(headers); - return builder.build(); - } - else { - return decoded; - } - } - catch (IOException e) { - throw new MessagingException("Failed to decode", e); - } - } - - @Override - public String getComponentType() { - return "converter"; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/CompositeCodec.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/CompositeCodec.java deleted file mode 100644 index 3e53f497787..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/CompositeCodec.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.integration.util.ClassUtils; -import org.springframework.util.Assert; - -/** - * An implementation of {@link Codec} that combines multiple codecs into a single codec, - * delegating encoding and decoding operations to the appropriate type-specific codec. - * This implementation associates object types with their appropriate codecs while providing a fallback default codec - * for unregistered types. - * This class uses {@code ClassUtils.findClosestMatch} to select the appropriate codec for a given object type. - * When multiple codecs match an object type, {@code ClassUtils.findClosestMatch} offers the - * {@code failOnTie} option. If {@code failOnTie} is {@code false}, it will return any one of the matching codecs. - * If {@code failOnTie} is {@code true} and multiple codecs match, it will throw an {@code IllegalStateException}. - * {@link CompositeCodec} sets {@code failOnTie} to {@code true}, so if multiple codecs match, an - * {@code IllegalStateException} is thrown. - * - * @author David Turanski - * @author Glenn Renfro - * - * @since 4.2 - */ -public class CompositeCodec implements Codec { - - private final Codec defaultCodec; - - private final Map, Codec> delegates; - - public CompositeCodec(Map, Codec> delegates, Codec defaultCodec) { - this.defaultCodec = defaultCodec; - Assert.notEmpty(delegates, "delegates must not be empty"); - this.delegates = new HashMap<>(delegates); - } - - @Override - public void encode(Object object, OutputStream outputStream) throws IOException { - Assert.notNull(object, "cannot encode a null object"); - Assert.notNull(outputStream, "'outputStream' cannot be null"); - findDelegate(object.getClass()).encode(object, outputStream); - } - - @Override - public byte[] encode(Object object) throws IOException { - Assert.notNull(object, "cannot encode a null object"); - return findDelegate(object.getClass()).encode(object); - } - - @Override - public T decode(InputStream inputStream, Class type) throws IOException { - Assert.notNull(inputStream, "'inputStream' cannot be null"); - Assert.notNull(type, "'type' cannot be null"); - return findDelegate(type).decode(inputStream, type); - } - - @Override - public T decode(byte[] bytes, Class type) throws IOException { - return decode(new ByteArrayInputStream(bytes), type); - } - - private Codec findDelegate(Class type) { - Class clazz = ClassUtils.findClosestMatch(type, this.delegates.keySet(), true); - return clazz == null ? this.defaultCodec : this.delegates.getOrDefault(clazz, this.defaultCodec); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/AbstractKryoCodec.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/AbstractKryoCodec.java deleted file mode 100644 index 09c7f1fadc8..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/AbstractKryoCodec.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.io.Input; -import com.esotericsoftware.kryo.io.Output; -import com.esotericsoftware.kryo.util.Pool; - -import org.springframework.integration.codec.Codec; -import org.springframework.util.Assert; - -/** - * Base class for {@link Codec}s using {@link Kryo}. - * Manages pooled {@link Kryo} instances. - * - * @author David Turanski - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 4.2 - */ -public abstract class AbstractKryoCodec implements Codec { - - protected final Pool pool; // NOSONAR final - - protected AbstractKryoCodec() { - this.pool = new Pool<>(true, true) { - - @Override - protected Kryo create() { - Kryo kryo = new Kryo(); - kryo.setRegistrationRequired(true); - // configure Kryo instance, customize settings - configureKryoInstance(kryo); - return kryo; - } - - }; - } - - @Override - public void encode(final Object object, OutputStream outputStream) { - Assert.notNull(object, "cannot encode a null object"); - Assert.notNull(outputStream, "'outputSteam' cannot be null"); - - Kryo kryo = this.pool.obtain(); - try (Output output = (outputStream instanceof Output castOutput ? castOutput : new Output(outputStream))) { - doEncode(kryo, object, output); - } - finally { - this.pool.free(kryo); - } - - } - - @Override - public T decode(byte[] bytes, Class type) throws IOException { - Assert.notNull(bytes, "'bytes' cannot be null"); - try (Input input = new Input(bytes)) { - return decode(input, type); - } - } - - @Override - public T decode(InputStream inputStream, final Class type) { - Assert.notNull(inputStream, "'inputStream' cannot be null"); - Assert.notNull(type, "'type' cannot be null"); - - Kryo kryo = this.pool.obtain(); - try (Input input = (inputStream instanceof Input castInput ? castInput : new Input(inputStream))) { - return doDecode(kryo, input, type); - } - finally { - this.pool.free(kryo); - } - } - - @Override - public byte[] encode(Object object) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - encode(object, bos); - byte[] bytes = bos.toByteArray(); - bos.close(); - return bytes; - } - - /** - * Subclasses implement this method to encode with Kryo. - * @param kryo the Kryo instance - * @param object the object to encode - * @param output the Kryo Output instance - */ - protected abstract void doEncode(Kryo kryo, Object object, Output output); - - /** - * Subclasses implement this method to decode with Kryo. - * @param kryo the Kryo instance - * @param input the Kryo Input instance - * @param type the class of the decoded object - * @param the type for decoded object - * @return the decoded object - */ - protected abstract T doDecode(Kryo kryo, Input input, Class type); - - /** - * Subclasses implement this to configure the kryo instance. This is invoked on each new Kryo instance - * when it is created. - * @param kryo the Kryo instance - */ - protected abstract void configureKryoInstance(Kryo kryo); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/AbstractKryoRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/AbstractKryoRegistrar.java deleted file mode 100644 index bae429d1d74..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/AbstractKryoRegistrar.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.Registration; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Base class for {@link KryoRegistrar} implementations. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 4.2 - */ -public abstract class AbstractKryoRegistrar implements KryoRegistrar { - - protected static final Kryo KRYO = new Kryo(); - - static { - KRYO.setRegistrationRequired(false); - } - - protected final Log log = LogFactory.getLog(getClass()); // NOSONAR property is final - - @Override - public void registerTypes(Kryo kryo) { - for (Registration registration : getRegistrations()) { - register(kryo, registration); - } - } - - private void register(Kryo kryo, Registration registration) { - int id = registration.getId(); - - Registration existing = kryo.getRegistration(id); - - if (existing != null) { - throw new IllegalStateException("registration already exists " + existing); - } - - if (this.log.isInfoEnabled()) { - this.log.info(String.format("registering %s with serializer %s", registration, - registration.getSerializer().getClass().getName())); - } - - kryo.register(registration); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/CompositeKryoRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/CompositeKryoRegistrar.java deleted file mode 100644 index a5e24ba45c7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/CompositeKryoRegistrar.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.ArrayList; -import java.util.List; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.Registration; - -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * A {@link KryoRegistrar} that delegates and validates registrations across all components. - * - * @author David Turanski - * @since 4.2 - */ -public class CompositeKryoRegistrar extends AbstractKryoRegistrar { - - private final List delegates; - - public CompositeKryoRegistrar(List delegates) { - this.delegates = new ArrayList(delegates); - - if (!CollectionUtils.isEmpty(this.delegates)) { - validateRegistrations(); - } - } - - @Override - public void registerTypes(Kryo kryo) { - for (KryoRegistrar registrar : this.delegates) { - registrar.registerTypes(kryo); - } - } - - @Override - public final List getRegistrations() { - List registrations = new ArrayList(); - for (KryoRegistrar registrar : this.delegates) { - registrations.addAll(registrar.getRegistrations()); - } - return registrations; - } - - private void validateRegistrations() { - List ids = new ArrayList(); - List> types = new ArrayList>(); - - for (Registration registration : getRegistrations()) { - Assert.isTrue(registration.getId() >= MIN_REGISTRATION_VALUE, - "registration ID must be >= " + MIN_REGISTRATION_VALUE); - if (ids.contains(registration.getId())) { - throw new IllegalArgumentException(String.format("Duplicate registration ID found: %d", - registration.getId())); - } - ids.add(registration.getId()); - - if (types.contains(registration.getType())) { - throw new IllegalArgumentException(String.format("Duplicate registration found for type: %s", - registration.getType())); - } - types.add(registration.getType()); - - if (log.isInfoEnabled()) { - log.info(String.format("configured Kryo registration %s with serializer %s", registration, - registration.getSerializer().getClass().getName())); - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/FileKryoRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/FileKryoRegistrar.java deleted file mode 100644 index bc945628c58..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/FileKryoRegistrar.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.io.File; -import java.util.Collections; -import java.util.List; - -import com.esotericsoftware.kryo.Registration; - -/** - * A {@link KryoRegistrar} used to validateRegistration a File serializer. - * - * @author David Turanski - * @author Gary Russell - * @since 4.2 - */ -public class FileKryoRegistrar extends AbstractKryoRegistrar { - - private final int registrationId; - - private final FileSerializer fileSerializer = new FileSerializer(); - - public FileKryoRegistrar() { - this.registrationId = RegistrationIds.DEFAULT_FILE_REGISTRATION_ID; - } - - /** - * - * @param registrationId overrides the default registration ID. - */ - public FileKryoRegistrar(int registrationId) { - this.registrationId = registrationId; - } - - @Override - public List getRegistrations() { - return Collections.singletonList(new Registration(File.class, this.fileSerializer, this.registrationId)); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/FileSerializer.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/FileSerializer.java deleted file mode 100644 index 44926954d80..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/FileSerializer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.io.File; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.Serializer; -import com.esotericsoftware.kryo.io.Input; -import com.esotericsoftware.kryo.io.Output; - -/** - * A custom Kryo {@link Serializer} for serializing File payloads. - * It serializes the file path and creates a new File instance to preserve the original path. - * File does not preserve the absolute otherwise as prefixLength - * is declared transient. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 4.2 - */ -public class FileSerializer extends Serializer { - - @Override - public void write(Kryo kryo, Output output, File file) { - output.writeString(file.getPath()); - } - - @Override - public File read(Kryo kryo, Input input, Class type) { - String path = input.readString(); - return new File(path); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoClassListRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoClassListRegistrar.java deleted file mode 100644 index 46185f95b11..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoClassListRegistrar.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.esotericsoftware.kryo.Registration; - -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * A {@link KryoRegistrar} used to validateRegistration a - * list of Java classes. This assigns a sequential registration ID starting with an initial value (50 by default), but - * may be configured. This is easiest to set up but requires that every server node be configured with the identical - * list in the same order. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 4.2 - */ -public class KryoClassListRegistrar extends AbstractKryoRegistrar { - - private static final int DEFAULT_INITIAL_ID = 50; - - private final List> registeredClasses; - - private int initialValue = DEFAULT_INITIAL_ID; - - /** - * @param classes the vararg of classes to validateRegistration - */ - public KryoClassListRegistrar(Class... classes) { - this(Arrays.asList(classes)); - } - - /** - * @param classes the list of classes to validateRegistration - */ - public KryoClassListRegistrar(List> classes) { - this.registeredClasses = new ArrayList<>(classes); - } - - /** - * Set the initial ID value. Classes in the list will be sequentially assigned an ID starting with this value - * (default is 50). - * @param initialValue the initial value - */ - public void setInitialValue(int initialValue) { - Assert.isTrue(initialValue >= MIN_REGISTRATION_VALUE, "'initialValue' must be >= " + MIN_REGISTRATION_VALUE); - this.initialValue = initialValue; - } - - @Override - public List getRegistrations() { - List registrations = new ArrayList<>(); - if (!CollectionUtils.isEmpty(this.registeredClasses)) { - for (int i = 0; i < this.registeredClasses.size(); i++) { - registrations.add(new Registration(this.registeredClasses.get(i), - KRYO.getSerializer(this.registeredClasses.get(i)), i + this.initialValue)); - } - } - return registrations; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoClassMapRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoClassMapRegistrar.java deleted file mode 100644 index 411a2f2ebfd..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoClassMapRegistrar.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.esotericsoftware.kryo.Registration; - -import org.springframework.util.CollectionUtils; - -/** - * A {@link KryoRegistrar} implementation backed by a Map - * used to explicitly set the registration ID for each class. - * - * @author David Turanski - * @since 4.2 - */ -public class KryoClassMapRegistrar extends AbstractKryoRegistrar { - - private final Map> registeredClasses; - - public KryoClassMapRegistrar(Map> kryoRegisteredClasses) { - this.registeredClasses = new HashMap>(kryoRegisteredClasses); - } - - @Override - public List getRegistrations() { - List registrations = new ArrayList(); - if (!CollectionUtils.isEmpty(this.registeredClasses)) { - for (Map.Entry> entry : this.registeredClasses.entrySet()) { - registrations.add( - new Registration(entry.getValue(), KRYO.getSerializer(entry.getValue()), entry.getKey())); - } - } - return registrations; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoRegistrar.java deleted file mode 100644 index 95f117fb4c4..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoRegistrar.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.List; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.Registration; - -/** - * Strategy interface used by {@link PojoCodec} to configure registrations - * classes consistently across {@link Kryo} instances. - * By default, user defined types are not registered to Kryo. - * Registration allows a unique ID (small positive integer is ideal) to represent the type - * in the byte stream. In a distributed environment, all Kryo instances must maintain a - * consistent registration configuration in order for serialization to function properly. - * Registrations can result in better performance in demanding situations, - * but requires some care to maintain. Use this feature only if you really need it. - * - * @author David Turanski - * @since 4.2 - */ -public interface KryoRegistrar { - - int MIN_REGISTRATION_VALUE = 10; - - /** - * This method is invoked by the {@link PojoCodec} and - * applied to the {@link Kryo} instance whenever a new instance is created. - * @param kryo the Kryo instance - */ - void registerTypes(Kryo kryo); - - /** - * - * @return the list of {@link Registration} provided - */ - List getRegistrations(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoRegistrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoRegistrationRegistrar.java deleted file mode 100644 index fdfe54a99d4..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/KryoRegistrationRegistrar.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.ArrayList; -import java.util.List; - -import com.esotericsoftware.kryo.Registration; - -/** - * A {@link KryoRegistrar} implementation backed by a List of {@link Registration}. - * - * @author David Turanski - * @since 4.2 - */ -public class KryoRegistrationRegistrar extends AbstractKryoRegistrar { - - private final List registrations; - - public KryoRegistrationRegistrar(List registrations) { - this.registrations = registrations != null - ? new ArrayList(registrations) - : new ArrayList(); - } - - @Override - public List getRegistrations() { - return this.registrations; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageCodec.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageCodec.java deleted file mode 100644 index e810a8f8b3c..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageCodec.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -/** - * {@link PojoCodec} configured to encode/decode {@code Message}s. - * - * @author Gary Russell - * - * @since 4.2 - * - */ -public class MessageCodec extends PojoCodec { - - /** - * Construct an instance using the default registration ids for message - * headers. - */ - public MessageCodec() { - super(new MessageKryoRegistrar()); - } - - /** - * Construct an instance using a custom registrar - usually used if different - * registration ids are required for message headers. - * @param registrar the registrar. - */ - public MessageCodec(MessageKryoRegistrar registrar) { - super(registrar); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageHeadersSerializer.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageHeadersSerializer.java deleted file mode 100644 index f6a569cfb93..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageHeadersSerializer.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.HashMap; -import java.util.Map; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.Serializer; -import com.esotericsoftware.kryo.io.Input; -import com.esotericsoftware.kryo.io.Output; - -import org.springframework.messaging.MessageHeaders; - -/** - * Kryo Serializer for {@link MessageHeaders}. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 4.2 - */ -class MessageHeadersSerializer extends Serializer { - - @Override - public void write(Kryo kryo, Output output, MessageHeaders headers) { - HashMap map = new HashMap<>(); - for (Map.Entry entry : headers.entrySet()) { - if (entry.getValue() != null) { - map.put(entry.getKey(), entry.getValue()); - } - } - kryo.writeObject(output, map); - } - - @Override - public MessageHeaders read(Kryo kryo, Input input, Class type) { - @SuppressWarnings("unchecked") - Map headers = kryo.readObject(input, HashMap.class); - return new MessageHeaders(headers); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageKryoRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageKryoRegistrar.java deleted file mode 100644 index 8eb5358427a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MessageKryoRegistrar.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.Registration; - -import org.springframework.integration.message.AdviceMessage; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.MutableMessageHeaders; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; - -/** - * Registers common MessageHeader types and Serializers. - * - * @author David Turanski - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.2 - */ -public class MessageKryoRegistrar extends AbstractKryoRegistrar { - - private int genericMessageRegistrationId = RegistrationIds.DEFAULT_GENERIC_MESSAGE_ID; - - private int errorMessageRegistrationId = RegistrationIds.DEFAULT_ERROR_MESSAGE_ID; - - private int adviceMessageRegistrationId = RegistrationIds.DEFAULT_ADVICE_MESSAGE_ID; - - private int mutableMessageRegistrationId = RegistrationIds.DEFAULT_MUTABLE_MESSAGE_ID; - - private int messageHeadersRegistrationId = RegistrationIds.DEFAULT_MESSAGEHEADERS_ID; - - private int mutableMessageHeadersRegistrationId = RegistrationIds.DEFAULT_MUTABLE_MESSAGEHEADERS_ID; - - private int hashMapRegistrationId = RegistrationIds.DEFAULT_HASH_MAP_ID; - - private int uuidRegistrationId = RegistrationIds.DEFAULT_UUID_ID; - - /** - * Set the registration id for {@link MessageHeaders}. - * @param messageHeadersRegistrationId the id, default 41. - */ - public void setMessageHeadersRegistrationId(int messageHeadersRegistrationId) { - this.messageHeadersRegistrationId = messageHeadersRegistrationId; - } - - /** - * Set the registration id for {@link MutableMessageHeaders}. - * @param mutableMessageHeadersRegistrationId the id, default 42. - */ - public void setMutableMessageHeadersRegistrationId(int mutableMessageHeadersRegistrationId) { - this.mutableMessageHeadersRegistrationId = mutableMessageHeadersRegistrationId; - } - - /** - * Set the registration id for {@link GenericMessage}. - * @param genericMessageRegistrationId the id, default 43. - * @since 4.3.23 - */ - public void setGenericMessageRegistrationId(int genericMessageRegistrationId) { - this.genericMessageRegistrationId = genericMessageRegistrationId; - } - - /** - * Set the registration id for {@link ErrorMessage}. - * @param errorMessageRegistrationId the id, default 44. - * @since 4.3.23 - */ - public void setErrorMessageRegistrationId(int errorMessageRegistrationId) { - this.errorMessageRegistrationId = errorMessageRegistrationId; - } - - /** - * Set the registration id for {@link AdviceMessage}. - * @param adviceMessageRegistrationId the id, default 45. - * @since 4.3.23 - */ - public void setAdviceMessageRegistrationId(int adviceMessageRegistrationId) { - this.adviceMessageRegistrationId = adviceMessageRegistrationId; - } - - /** - * Set the registration id for {@link MutableMessage}. - * @param mutableMessageRegistrationId the id, default 46. - * @since 4.3.23 - */ - public void setMutableMessageRegistrationId(int mutableMessageRegistrationId) { - this.mutableMessageRegistrationId = mutableMessageRegistrationId; - } - - /** - * Set the registration id for {@link HashMap}. - * @param hashMapRegistrationId the id, default 47. - * @since 4.3.23 - */ - public void setHashMapRegistrationId(int hashMapRegistrationId) { - this.hashMapRegistrationId = hashMapRegistrationId; - } - - /** - * Set the registration id for {@link UUID}. - * @param uuidRegistrationId the id, default 48. - * @since 4.3.23 - */ - public void setUuidRegistrationId(int uuidRegistrationId) { - this.uuidRegistrationId = uuidRegistrationId; - } - - @Override - public void registerTypes(Kryo kryo) { - super.registerTypes(kryo); - kryo.register(GenericMessage.class, this.genericMessageRegistrationId); - kryo.register(ErrorMessage.class, this.errorMessageRegistrationId); - kryo.register(AdviceMessage.class, this.adviceMessageRegistrationId); - kryo.register(MutableMessage.class, this.mutableMessageRegistrationId); - kryo.register(HashMap.class, this.hashMapRegistrationId); - kryo.register(UUID.class, this.uuidRegistrationId); - } - - @Override - public List getRegistrations() { - return Arrays.asList( - new Registration(MessageHeaders.class, new MessageHeadersSerializer(), - this.messageHeadersRegistrationId), - new Registration(MutableMessageHeaders.class, new MutableMessageHeadersSerializer(), - this.mutableMessageHeadersRegistrationId)); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MutableMessageHeadersSerializer.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MutableMessageHeadersSerializer.java deleted file mode 100644 index b1501b22ed8..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/MutableMessageHeadersSerializer.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.HashMap; -import java.util.Map; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.io.Input; - -import org.springframework.integration.support.MutableMessageHeaders; -import org.springframework.messaging.MessageHeaders; - -/** - * Kryo Serializer for {@link MutableMessageHeaders}. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 4.2 - */ -class MutableMessageHeadersSerializer extends MessageHeadersSerializer { - - @Override - public MessageHeaders read(Kryo kryo, Input input, Class type) { - @SuppressWarnings("unchecked") - Map headers = kryo.readObject(input, HashMap.class); - return new MutableMessageHeaders(headers); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/PojoCodec.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/PojoCodec.java deleted file mode 100644 index 01669469d62..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/PojoCodec.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -import java.util.Collections; -import java.util.List; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.io.Input; -import com.esotericsoftware.kryo.io.Output; -import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy; -import org.jspecify.annotations.Nullable; -import org.objenesis.strategy.StdInstantiatorStrategy; - -import org.springframework.util.CollectionUtils; - -/** - * Kryo Codec that can encode and decode arbitrary types. Classes and associated - * {@link com.esotericsoftware.kryo.Serializer}s may be registered via - * {@link KryoRegistrar}s. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 4.2 - */ -public class PojoCodec extends AbstractKryoCodec { - - private final @Nullable CompositeKryoRegistrar kryoRegistrar; - - private final boolean useReferences; - - public PojoCodec() { - this.kryoRegistrar = null; - this.useReferences = true; - } - - /** - * Create an instance with a single KryoRegistrar. - * @param kryoRegistrar the registrar. - */ - public PojoCodec(@Nullable KryoRegistrar kryoRegistrar) { - this(kryoRegistrar != null ? Collections.singletonList(kryoRegistrar) : null, true); - } - - /** - * Create an instance with zero to many KryoRegistrars. - * @param kryoRegistrars a list KryoRegistrars. - */ - public PojoCodec(List kryoRegistrars) { - this.kryoRegistrar = CollectionUtils.isEmpty(kryoRegistrars) ? null : - new CompositeKryoRegistrar(kryoRegistrars); - this.useReferences = true; - } - - /** - * Create an instance with a single KryoRegistrar. - * @param kryoRegistrar the registrar. - * @param useReferences set to false if references are not required (if the object graph is known to be acyclical). - * The default is 'true' which is less performant but more flexible. - */ - public PojoCodec(@Nullable KryoRegistrar kryoRegistrar, boolean useReferences) { - this(kryoRegistrar != null ? Collections.singletonList(kryoRegistrar) : null, useReferences); - } - - /** - * Create an instance with zero to many KryoRegistrars. - * @param kryoRegistrars a list KryoRegistrars. - * @param useReferences set to false if references are not required (if the object graph is known to be acyclical). - * The default is 'true' which is less performant but more flexible. - */ - public PojoCodec(@Nullable List kryoRegistrars, boolean useReferences) { - this.kryoRegistrar = CollectionUtils.isEmpty(kryoRegistrars) ? null : - new CompositeKryoRegistrar(kryoRegistrars); - this.useReferences = useReferences; - } - - @Override - protected void doEncode(Kryo kryo, Object object, Output output) { - kryo.writeObject(output, object); - } - - @Override - protected T doDecode(Kryo kryo, Input input, Class type) { - return kryo.readObject(input, type); - } - - @Override - protected void configureKryoInstance(Kryo kryo) { - kryo.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); - if (this.kryoRegistrar != null) { - this.kryoRegistrar.registerTypes(kryo); - } - kryo.setReferences(this.useReferences); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/RegistrationIds.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/RegistrationIds.java deleted file mode 100644 index d863f2ea368..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/RegistrationIds.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.codec.kryo; - -/** - * Default registration ids for serializers provided by the framework. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.2 - * - */ -public final class RegistrationIds { - - public static final int DEFAULT_FILE_REGISTRATION_ID = 40; - - public static final int DEFAULT_MESSAGEHEADERS_ID = 41; - - public static final int DEFAULT_MUTABLE_MESSAGEHEADERS_ID = 42; - - public static final int DEFAULT_GENERIC_MESSAGE_ID = 43; - - public static final int DEFAULT_ERROR_MESSAGE_ID = 44; - - public static final int DEFAULT_ADVICE_MESSAGE_ID = 45; - - public static final int DEFAULT_MUTABLE_MESSAGE_ID = 46; - - public static final int DEFAULT_HASH_MAP_ID = 47; - - public static final int DEFAULT_UUID_ID = 48; - - private RegistrationIds() { - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/package-info.java deleted file mode 100644 index f39d6ef9e14..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/kryo/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * The Kryo specific {@code Codec} classes. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.codec.kryo; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/codec/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/codec/package-info.java deleted file mode 100644 index cc00e06a24b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/codec/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides base classes for the {@code Codec} abstraction. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.codec; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractEvaluationContextFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractEvaluationContextFactoryBean.java deleted file mode 100644 index 9d41ca36067..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractEvaluationContextFactoryBean.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Method; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.convert.ConversionService; -import org.springframework.expression.IndexAccessor; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypeConverter; -import org.springframework.expression.spel.support.StandardTypeConverter; -import org.springframework.integration.expression.SpelPropertyAccessorRegistrar; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.util.Assert; - -/** - * Abstract class for integration evaluation context factory beans. - * - * @author Gary Russell - * - * @since 4.3.15 - * - */ -public abstract class AbstractEvaluationContextFactoryBean implements ApplicationContextAware, InitializingBean { - - private Map propertyAccessors = new LinkedHashMap<>(); - - private Map indexAccessors = new LinkedHashMap<>(); - - private Map functions = new LinkedHashMap<>(); - - private TypeConverter typeConverter = new StandardTypeConverter(); - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - @Nullable - private SpelPropertyAccessorRegistrar propertyAccessorRegistrar; - - private boolean initialized; - - protected TypeConverter getTypeConverter() { - return this.typeConverter; - } - - protected ApplicationContext getApplicationContext() { - return this.applicationContext; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - public void setPropertyAccessors(Map propertyAccessors) { - Assert.isTrue(!this.initialized, "'propertyAccessors' can't be changed after initialization."); - Assert.notNull(propertyAccessors, "'propertyAccessors' must not be null."); - Assert.noNullElements(propertyAccessors.values().toArray(), "'propertyAccessors' cannot have null values."); - this.propertyAccessors = new LinkedHashMap<>(propertyAccessors); - } - - public Map getPropertyAccessors() { - return this.propertyAccessors; - } - - /** - * Set a map of {@link IndexAccessor}s to use in the target {@link org.springframework.expression.EvaluationContext} - * @param indexAccessors the map of {@link IndexAccessor}s to use - * @since 6.4 - * @see org.springframework.expression.EvaluationContext#getIndexAccessors() - */ - public void setIndexAccessors(Map indexAccessors) { - Assert.isTrue(!this.initialized, "'indexAccessors' can't be changed after initialization."); - Assert.notNull(indexAccessors, "'indexAccessors' must not be null."); - Assert.noNullElements(indexAccessors.values().toArray(), "'indexAccessors' cannot have null values."); - this.indexAccessors = new LinkedHashMap<>(indexAccessors); - } - - /** - * Return the map of {@link IndexAccessor}s to use in the target {@link org.springframework.expression.EvaluationContext} - * @return the map of {@link IndexAccessor}s to use - * @since 6.4 - * @see org.springframework.expression.EvaluationContext#getIndexAccessors() - */ - public Map getIndexAccessors() { - return this.indexAccessors; - } - - public void setFunctions(Map functionsArg) { - Assert.isTrue(!this.initialized, "'functions' can't be changed after initialization."); - Assert.notNull(functionsArg, "'functions' must not be null."); - Assert.noNullElements(functionsArg.values().toArray(), "'functions' cannot have null values."); - this.functions = new LinkedHashMap<>(functionsArg); - } - - public Map getFunctions() { - return this.functions; - } - - protected void initialize(String beanName) { - if (this.applicationContext != null) { - conversionService(); - functions(); - try { - this.propertyAccessorRegistrar = this.applicationContext.getBean(SpelPropertyAccessorRegistrar.class); - } - catch (@SuppressWarnings("unused") NoSuchBeanDefinitionException e) { - // There is no 'SpelPropertyAccessorRegistrar' bean in the application context. - } - propertyAccessors(); - indexAccessors(); - processParentIfPresent(beanName); - } - this.initialized = true; - } - - private void conversionService() { - ConversionService conversionService = IntegrationUtils.getConversionService(getApplicationContext()); - if (conversionService != null) { - this.typeConverter = new StandardTypeConverter(conversionService); - } - } - - private void functions() { - Map spelFunctions = - BeanFactoryUtils.beansOfTypeIncludingAncestors(this.applicationContext, SpelFunctionFactoryBean.class); - for (SpelFunctionFactoryBean spelFunctionFactoryBean : spelFunctions.values()) { - String functionName = spelFunctionFactoryBean.getFunctionName(); - if (!this.functions.containsKey(functionName)) { - this.functions.put(functionName, spelFunctionFactoryBean.getObject()); - } - } - } - - private void propertyAccessors() { - if (this.propertyAccessorRegistrar != null) { - propertyAccessors(this.propertyAccessorRegistrar.getPropertyAccessors()); - } - } - - private void propertyAccessors(Map propertyAccessors) { - for (Entry entry : propertyAccessors.entrySet()) { - String key = entry.getKey(); - if (!this.propertyAccessors.containsKey(key)) { - this.propertyAccessors.put(key, entry.getValue()); - } - } - } - - private void indexAccessors() { - if (this.propertyAccessorRegistrar != null) { - indexAccessors(this.propertyAccessorRegistrar.getIndexAccessors()); - } - } - - private void indexAccessors(Map indexAccessors) { - for (Entry entry : indexAccessors.entrySet()) { - String key = entry.getKey(); - if (!this.indexAccessors.containsKey(key)) { - this.indexAccessors.put(key, entry.getValue()); - } - } - } - - private void processParentIfPresent(String beanName) { - ApplicationContext parent = this.applicationContext.getParent(); - - if (parent != null && parent.containsBean(beanName)) { - AbstractEvaluationContextFactoryBean parentFactoryBean = parent.getBean("&" + beanName, getClass()); - propertyAccessors(parentFactoryBean.getPropertyAccessors()); - indexAccessors(parentFactoryBean.getIndexAccessors()); - for (Entry entry : parentFactoryBean.getFunctions().entrySet()) { - String key = entry.getKey(); - if (!this.functions.containsKey(key)) { - this.functions.put(key, entry.getValue()); - } - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java deleted file mode 100644 index a092be871e2..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java +++ /dev/null @@ -1,890 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.aopalliance.aop.Advice; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; -import org.springframework.aop.support.NameMatchMethodPointcut; -import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.FactoryBean; -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.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.parsing.ComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionValidationException; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.core.GenericTypeResolver; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.annotation.Order; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.task.TaskExecutor; -import org.springframework.core.type.MethodMetadata; -import org.springframework.core.type.StandardMethodMetadata; -import org.springframework.integration.annotation.IdempotentReceiver; -import org.springframework.integration.annotation.Poller; -import org.springframework.integration.annotation.Reactive; -import org.springframework.integration.annotation.Role; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.MessagePublishingErrorHandler; -import org.springframework.integration.config.annotation.MethodAnnotationPostProcessor; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.context.Orderable; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.endpoint.AbstractPollingEndpoint; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.endpoint.PollingConsumer; -import org.springframework.integration.endpoint.ReactiveStreamsConsumer; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.LambdaMessageProcessor; -import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; -import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper; -import org.springframework.integration.handler.advice.HandleMessageAdvice; -import org.springframework.integration.router.AbstractMessageRouter; -import org.springframework.integration.scheduling.PollerMetadata; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.integration.util.ClassUtils; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.core.DestinationResolutionException; -import org.springframework.messaging.core.DestinationResolver; -import org.springframework.scheduling.Trigger; -import org.springframework.scheduling.support.CronTrigger; -import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -/** - * Base class for Method-level annotation post-processors. - * - * @param the target annotation type. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * @author Ngoc Nhan - */ -public abstract class AbstractMethodAnnotationPostProcessor - implements MethodAnnotationPostProcessor, BeanFactoryAware { - - private static final String UNCHECKED = "unchecked"; - - protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - - protected static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain"; // NOSONAR - - protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout"; // NOSONAR - - protected final List messageHandlerAttributes = new ArrayList<>(); // NOSONAR - - protected final Class annotationType; - - @SuppressWarnings("NullAway.Init") - private ConfigurableListableBeanFactory beanFactory; - - @SuppressWarnings("NullAway.Init") - private BeanDefinitionRegistry definitionRegistry; - - @SuppressWarnings("NullAway.Init") - private ConversionService conversionService; - - @SuppressWarnings("NullAway.Init") - private volatile DestinationResolver channelResolver; - - @SuppressWarnings({"NullAway", "unchecked"}) - public AbstractMethodAnnotationPostProcessor() { - this.messageHandlerAttributes.add(SEND_TIMEOUT_ATTRIBUTE); - this.annotationType = - (Class) GenericTypeResolver.resolveTypeArgument(getClass(), MethodAnnotationPostProcessor.class); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; - this.definitionRegistry = (BeanDefinitionRegistry) beanFactory; - this.conversionService = - this.beanFactory.getConversionService() != null - ? this.beanFactory.getConversionService() - : DefaultConversionService.getSharedInstance(); - } - - protected ConfigurableListableBeanFactory getBeanFactory() { - return this.beanFactory; - } - - protected BeanDefinitionRegistry getDefinitionRegistry() { - return this.definitionRegistry; - } - - protected ConversionService getConversionService() { - return this.conversionService; - } - - protected DestinationResolver getChannelResolver() { - if (this.channelResolver == null) { - this.channelResolver = ChannelResolverUtils.getChannelResolver(this.beanFactory); - } - return this.channelResolver; - } - - @Override - public void processBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - List annotations) { - - ResolvableType handlerBeanType = getHandlerBeanClass(beanDefinition); - - String handlerBeanName = beanName; - - BeanDefinition handlerBeanDefinition = - resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); - MergedAnnotations mergedAnnotations = - Objects.requireNonNull(beanDefinition.getFactoryMethodMetadata()).getAnnotations(); - - if (handlerBeanDefinition != null) { - if (!handlerBeanDefinition.equals(beanDefinition)) { - String beanClassName = handlerBeanDefinition.getBeanClassName(); - Assert.notNull(beanClassName, "No bean class present for " + handlerBeanDefinition); - Class handlerBeanClass = - org.springframework.util.ClassUtils.resolveClassName(beanClassName, - this.beanFactory.getBeanClassLoader()); - - if (isClassIn(handlerBeanClass, Orderable.class, AbstractSimpleMessageHandlerFactoryBean.class)) { - mergedAnnotations.get(Order.class) - .getValue(AnnotationUtils.VALUE, String.class). - ifPresent((order) -> handlerBeanDefinition.getPropertyValues().add("order", order)); - } - - if (isClassIn(handlerBeanClass, AbstractMessageProducingHandler.class, AbstractMessageRouter.class, - AbstractSimpleMessageHandlerFactoryBean.class)) { - - new BeanDefinitionPropertiesMapper(handlerBeanDefinition, annotations) - .setPropertyValue(SEND_TIMEOUT_ATTRIBUTE) - .setPropertyValue("outputChannel", "outputChannelName") - .setPropertyValue("defaultOutputChannel", "defaultOutputChannelName"); - } - - if (isClassIn(handlerBeanClass, AbstractMessageProducingHandler.class, - AbstractSimpleMessageHandlerFactoryBean.class) - && !RouterFactoryBean.class.isAssignableFrom(handlerBeanClass)) { - - applyAdviceChainIfAny(handlerBeanDefinition, annotations); - } - - handlerBeanName = generateHandlerBeanName(beanName, mergedAnnotations); - this.definitionRegistry.registerBeanDefinition(handlerBeanName, handlerBeanDefinition); - } - } - else { - throw new BeanDefinitionValidationException( - "The messaging annotation processor for '" + this.annotationType + - "' cannot create a target handler bean. " + - "The bean definition with the problem is: " + beanName); - } - - BeanDefinition endpointBeanDefinition = - createEndpointBeanDefinition(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName), - new BeanComponentDefinition(beanDefinition, beanName), annotations); - - new BeanDefinitionPropertiesMapper(endpointBeanDefinition, annotations) - .setPropertyValue("autoStartup") - .setPropertyValue("phase"); - - Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class); - Reactive reactive = MessagingAnnotationUtils.resolveAttribute(annotations, "reactive", Reactive.class); - Assert.state(reactive == null || poller == null, - () -> "The 'poller' and 'reactive' attributes are mutually exclusive." + - "The bean definition with the problem is: " + beanName); - if (poller != null) { - applyPollerForEndpoint(endpointBeanDefinition, poller); - } - else if (reactive != null) { - BeanMetadataElement reactiveCustomizer; - String reactiveCustomizerBean = reactive.value(); - if (StringUtils.hasText(reactiveCustomizerBean)) { - reactiveCustomizer = new RuntimeBeanReference(reactiveCustomizerBean); - } - else { - reactiveCustomizer = - BeanDefinitionBuilder.genericBeanDefinition(Function.class) - .setFactoryMethod("identity") - .getBeanDefinition(); - } - endpointBeanDefinition.getPropertyValues().add("reactiveCustomizer", reactiveCustomizer); - } - - mergedAnnotations.get(Role.class) - .getValue(AnnotationUtils.VALUE, String.class). - ifPresent((role) -> endpointBeanDefinition.getPropertyValues().add("role", role)); - - String endpointBeanName = - generateHandlerBeanName(beanName, mergedAnnotations) - .replaceFirst("\\.(handler|source)$", ""); - this.beanFactory.registerDependentBean(beanName, endpointBeanName); - this.definitionRegistry.registerBeanDefinition(endpointBeanName, endpointBeanDefinition); - } - - private static boolean isClassIn(Class classToVerify, Class... classesToMatch) { - for (Class toMatch : classesToMatch) { - if (toMatch.isAssignableFrom(classToVerify)) { - return true; - } - } - return false; - } - - protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition, - ComponentDefinition beanDefinition, List annotations) { - - BeanDefinitionBuilder endpointDefinitionBuilder = - BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class) - .addPropertyReference("handler", handlerBeanDefinition.getName()); - - String inputChannelAttribute = getInputChannelAttribute(); - String inputChannelName = - MessagingAnnotationUtils.resolveAttribute(annotations, inputChannelAttribute, String.class); - Assert.hasText(inputChannelName, - () -> "The '" + inputChannelAttribute + "' attribute is required on '" + this.annotationType + - "' on @Bean method"); - if (!this.definitionRegistry.containsBeanDefinition(inputChannelName)) { - this.definitionRegistry.registerBeanDefinition(inputChannelName, - new RootBeanDefinition(DirectChannel.class)); - } - BeanDefinition endpointBeanDefinition = - endpointDefinitionBuilder.addPropertyReference("inputChannel", inputChannelName) - .getBeanDefinition(); - - applyAdviceChainIfAny(endpointBeanDefinition, annotations); - - return endpointBeanDefinition; - } - - private static void applyAdviceChainIfAny(BeanDefinition beanDefinition, List annotations) { - String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE, - String[].class); - - if (!ObjectUtils.isEmpty(adviceChainNames)) { - ManagedList adviceChain = - Arrays.stream(adviceChainNames) - .map(RuntimeBeanReference::new) - .collect(Collectors.toCollection(ManagedList::new)); - - beanDefinition.getPropertyValues().addPropertyValue("adviceChain", adviceChain); - } - } - - private static void applyPollerForEndpoint(BeanDefinition endpointBeanDefinition, Poller poller) { - BeanMetadataElement pollerMetadataBeanDefinition; - String ref = poller.value(); - if (StringUtils.hasText(ref)) { - pollerMetadataBeanDefinition = new RuntimeBeanReference(ref); - } - else { - BeanDefinitionBuilder pollerBeanDefinitionBuilder = - BeanDefinitionBuilder.genericBeanDefinition(PollerMetadata.class); - - new BeanDefinitionPropertiesMapper(pollerBeanDefinitionBuilder.getRawBeanDefinition(), List.of(poller)) - .setPropertyReference("trigger") - .setPropertyReference("taskExecutor") - .setPropertyValue("receiveTimeout") - .setPropertyValue("maxMessagesPerPoll"); - - String errorChannel = poller.errorChannel(); - if (StringUtils.hasText(errorChannel)) { - BeanDefinitionBuilder errorHandler = - BeanDefinitionBuilder.genericBeanDefinition(MessagePublishingErrorHandler.class) - .addPropertyReference("defaultErrorChannel", errorChannel); - pollerBeanDefinitionBuilder.addPropertyValue("errorHandler", errorHandler.getBeanDefinition()); - } - - String fixedDelay = poller.fixedDelay(); - String fixedRate = poller.fixedRate(); - String cron = poller.cron(); - BeanDefinition triggerBeanDefinition = null; - - if (StringUtils.hasText(cron)) { - triggerBeanDefinition = triggerBeanDefinition(CronTrigger.class, cron); - } - else if (StringUtils.hasText(fixedDelay)) { - triggerBeanDefinition = triggerBeanDefinition(PeriodicTrigger.class, fixedDelay); - } - else if (StringUtils.hasText(fixedRate)) { - triggerBeanDefinition = triggerBeanDefinition(PeriodicTrigger.class, fixedRate); - triggerBeanDefinition.getPropertyValues().addPropertyValue("fixedRate", true); - } - - if (triggerBeanDefinition != null) { - pollerBeanDefinitionBuilder.addPropertyValue("trigger", triggerBeanDefinition); - } - - pollerMetadataBeanDefinition = pollerBeanDefinitionBuilder.getBeanDefinition(); - } - - endpointBeanDefinition.getPropertyValues() - .addPropertyValue("pollerMetadata", pollerMetadataBeanDefinition); - } - - private static BeanDefinition triggerBeanDefinition(Class triggerClass, String triggerValue) { - return BeanDefinitionBuilder.genericBeanDefinition(triggerClass) - .addConstructorArgValue(triggerValue) - .getBeanDefinition(); - } - - private ResolvableType getHandlerBeanClass(AnnotatedBeanDefinition beanDefinition) { - MethodMetadata factoryMethodMetadata = beanDefinition.getFactoryMethodMetadata(); - if (factoryMethodMetadata instanceof StandardMethodMetadata standardMethodMetadata) { - return ResolvableType.forMethodReturnType(standardMethodMetadata.getIntrospectedMethod()); - } - else { - Assert.notNull(factoryMethodMetadata, "No factoryMethodMetadata present for " + beanDefinition); - String typeName = factoryMethodMetadata.getReturnTypeName(); - Class beanClass = - org.springframework.util.ClassUtils.resolveClassName(typeName, - this.beanFactory.getBeanClassLoader()); - return ResolvableType.forClass(beanClass); - } - } - - @Nullable - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotations) { - - Class classToCheck = handlerBeanType.toClass(); - - // Any out-of-the-box MessageHandler factory bean is considered as direct handler usage. - if (AbstractSimpleMessageHandlerFactoryBean.class.isAssignableFrom(classToCheck)) { - return beanDefinition; - } - - if (FactoryBean.class.isAssignableFrom(classToCheck)) { - classToCheck = this.beanFactory.getType(beanName); - } - Assert.state(classToCheck != null, "No handler bean found for " + beanName); - if (isClassIn(classToCheck, AbstractMessageProducingHandler.class, AbstractMessageRouter.class)) { - checkMessageHandlerAttributes(beanName, annotations); - return beanDefinition; - } - - return null; - } - - @Override - public Object postProcess(Object bean, String beanName, Method method, List annotations) { - MessageHandler handler = createHandler(bean, method, annotations); - - if (!(handler instanceof ReactiveMessageHandlerAdapter)) { - orderable(method, handler); - producerOrRouter(annotations, handler); - - handler = registerHandlerBean(beanName, method, handler); - - handler = annotated(method, handler); - handler = adviceChain(beanName, annotations, handler); - } - - AbstractEndpoint endpoint = createEndpoint(handler, method, annotations); - if (endpoint != null) { - return endpoint; - } - else { - return handler; - } - } - - private MessageHandler registerHandlerBean(String beanName, Method method, final MessageHandler handler) { - String handlerBeanName = generateHandlerBeanName(beanName, method); - if (handler instanceof ReplyProducingMessageHandlerWrapper - && StringUtils.hasText(MessagingAnnotationUtils.endpointIdValue(method))) { - handlerBeanName = handlerBeanName + ".wrapper"; - } - if (handler instanceof IntegrationObjectSupport integrationObjectSupport) { - integrationObjectSupport.setComponentName( - handlerBeanName.substring(0, - handlerBeanName.indexOf(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX))); - } - this.definitionRegistry.registerBeanDefinition(handlerBeanName, - new RootBeanDefinition(MessageHandler.class, () -> handler)); - return this.beanFactory.getBean(handlerBeanName, MessageHandler.class); - } - - private void producerOrRouter(List annotations, MessageHandler handler) { - if (handler instanceof AbstractMessageProducingHandler || handler instanceof AbstractMessageRouter) { - String sendTimeout = MessagingAnnotationUtils.resolveAttribute(annotations, "sendTimeout", String.class); - if (sendTimeout != null) { - String resolvedValue = this.beanFactory.resolveEmbeddedValue(sendTimeout); - if (resolvedValue != null) { - long value = Long.parseLong(resolvedValue); - if (handler instanceof AbstractMessageProducingHandler abstractMessageProducingHandler) { - abstractMessageProducingHandler.setSendTimeout(value); - } - else { - ((AbstractMessageRouter) handler).setSendTimeout(value); - } - } - } - } - } - - private MessageHandler annotated(Method method, MessageHandler handlerArg) { - MessageHandler handler = handlerArg; - if (AnnotatedElementUtils.isAnnotated(method, IdempotentReceiver.class.getName()) - && !AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - - String[] interceptors = - Objects.requireNonNull(AnnotationUtils.getAnnotation(method, IdempotentReceiver.class)).value(); - for (String interceptor : interceptors) { - DefaultBeanFactoryPointcutAdvisor advisor = new DefaultBeanFactoryPointcutAdvisor(); - advisor.setAdviceBeanName(interceptor); - NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); - pointcut.setMappedName("handleMessage"); - advisor.setPointcut(pointcut); - advisor.setBeanFactory(this.beanFactory); - - if (handler instanceof Advised advised) { - advised.addAdvisor(advisor); - } - else { - ProxyFactory proxyFactory = new ProxyFactory(handler); - proxyFactory.addAdvisor(advisor); - handler = (MessageHandler) proxyFactory.getProxy(this.beanFactory.getBeanClassLoader()); - } - } - } - return handler; - } - - private MessageHandler adviceChain(String beanName, List annotations, MessageHandler handlerArg) { - MessageHandler handler = handlerArg; - List adviceChain = extractAdviceChain(beanName, annotations); - - if (!CollectionUtils.isEmpty(adviceChain) && handler instanceof AbstractReplyProducingMessageHandler abstractReplyProducingMessageHandler) { - abstractReplyProducingMessageHandler.setAdviceChain(adviceChain); - } - - if (!CollectionUtils.isEmpty(adviceChain)) { - for (Advice advice : adviceChain) { - if (advice instanceof HandleMessageAdvice) { - NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(advice); - handlerAdvice.addMethodName("handleMessage"); - if (handler instanceof Advised advised) { - advised.addAdvisor(handlerAdvice); - } - else { - ProxyFactory proxyFactory = new ProxyFactory(handler); - proxyFactory.addAdvisor(handlerAdvice); - handler = (MessageHandler) proxyFactory.getProxy(this.beanFactory.getBeanClassLoader()); - } - } - } - } - return handler; - } - - protected @Nullable List extractAdviceChain(String beanName, List annotations) { - List adviceChain = null; - String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE, - String[].class); - /* - * Note: we don't merge advice chain contents; if the directAnnotation has a non-empty - * attribute, it wins. You cannot "remove" an advice chain from a meta-annotation - * by setting an empty array on the custom annotation. - */ - if (adviceChainNames != null && adviceChainNames.length > 0) { - adviceChain = new ArrayList<>(); - for (String adviceChainName : adviceChainNames) { - Object adviceChainBean = this.beanFactory.getBean(adviceChainName); - if (adviceChainBean instanceof Advice advice) { - adviceChain.add(advice); - } - else if (adviceChainBean instanceof Advice[] advices) { - Collections.addAll(adviceChain, advices); - } - else if (adviceChainBean instanceof Collection) { - @SuppressWarnings(UNCHECKED) - Collection adviceChainEntries = (Collection) adviceChainBean; - adviceChain.addAll(adviceChainEntries); - } - else { - throw new IllegalArgumentException("Invalid advice chain type:" + - adviceChainName.getClass().getName() + " for bean '" + beanName + "'"); - } - } - } - return adviceChain; - } - - protected @Nullable AbstractEndpoint createEndpoint(MessageHandler handler, @SuppressWarnings("unused") Method method, - List annotations) { - - AbstractEndpoint endpoint = null; - String inputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), - String.class); - if (StringUtils.hasText(inputChannelName)) { - MessageChannel inputChannel; - try { - inputChannel = getChannelResolver().resolveDestination(inputChannelName); - } - catch (DestinationResolutionException e) { - if (e.getCause() instanceof NoSuchBeanDefinitionException) { - this.definitionRegistry.registerBeanDefinition(inputChannelName, - new RootBeanDefinition(DirectChannel.class, DirectChannel::new)); - inputChannel = this.beanFactory.getBean(inputChannelName, MessageChannel.class); - } - else { - throw e; - } - } - Assert.notNull(inputChannel, () -> "failed to resolve inputChannel '" + inputChannelName + "'"); - - endpoint = doCreateEndpoint(handler, inputChannel, annotations); - } - return endpoint; - } - - protected AbstractEndpoint doCreateEndpoint(MessageHandler handler, MessageChannel inputChannel, - List annotations) { - - Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class); - - Reactive reactive = MessagingAnnotationUtils.resolveAttribute(annotations, "reactive", Reactive.class); - - Assert.state(reactive == null || poller == null, - "The 'poller' and 'reactive' are mutually exclusive."); - - if (inputChannel instanceof Publisher || handler instanceof ReactiveMessageHandlerAdapter || reactive != null) { - return reactiveStreamsConsumer(inputChannel, handler, reactive); - } - else if (inputChannel instanceof SubscribableChannel subscribableChannel) { - Assert.state(poller == null, () -> - "A '@Poller' should not be specified for Annotation-based " + - "endpoint, since '" + inputChannel + "' is a SubscribableChannel (not pollable)."); - return new EventDrivenConsumer(subscribableChannel, handler); - } - else if (inputChannel instanceof PollableChannel) { - return pollingConsumer(inputChannel, handler, poller); - } - else { - throw new IllegalArgumentException("Unsupported 'inputChannel' type: '" - + inputChannel.getClass().getName() + "'. " + - "Must be one of 'SubscribableChannel', 'PollableChannel' or 'ReactiveStreamsSubscribableChannel'"); - } - } - - private ReactiveStreamsConsumer reactiveStreamsConsumer(MessageChannel channel, MessageHandler handler, - @Nullable Reactive reactive) { - - ReactiveStreamsConsumer reactiveStreamsConsumer; - if (handler instanceof ReactiveMessageHandlerAdapter reactiveMessageHandlerAdapter) { - reactiveStreamsConsumer = new ReactiveStreamsConsumer(channel, - reactiveMessageHandlerAdapter.getDelegate()); - } - else { - reactiveStreamsConsumer = new ReactiveStreamsConsumer(channel, handler); - } - - if (reactive != null) { - String functionBeanName = reactive.value(); - if (StringUtils.hasText(functionBeanName)) { - @SuppressWarnings(UNCHECKED) - Function>, ? extends Publisher>> reactiveCustomizer = - this.beanFactory.getBean(functionBeanName, Function.class); - reactiveStreamsConsumer.setReactiveCustomizer(reactiveCustomizer); - } - } - - return reactiveStreamsConsumer; - } - - private PollingConsumer pollingConsumer(MessageChannel inputChannel, MessageHandler handler, @Nullable Poller poller) { - PollingConsumer pollingConsumer = new PollingConsumer((PollableChannel) inputChannel, handler); - configurePollingEndpoint(pollingConsumer, poller); - return pollingConsumer; - } - - protected void configurePollingEndpoint(AbstractPollingEndpoint pollingEndpoint, @Nullable Poller poller) { - PollerMetadata pollerMetadata; - if (poller != null) { - String ref = poller.value(); - String triggerRef = poller.trigger(); - String executorRef = poller.taskExecutor(); - String fixedDelayValue = this.beanFactory.resolveEmbeddedValue(poller.fixedDelay()); - String fixedRateValue = this.beanFactory.resolveEmbeddedValue(poller.fixedRate()); - String maxMessagesPerPollValue = this.beanFactory.resolveEmbeddedValue(poller.maxMessagesPerPoll()); - String cron = this.beanFactory.resolveEmbeddedValue(poller.cron()); - String errorChannel = this.beanFactory.resolveEmbeddedValue(poller.errorChannel()); - String receiveTimeout = this.beanFactory.resolveEmbeddedValue(poller.receiveTimeout()); - - if (StringUtils.hasText(ref)) { - Assert.state(!StringUtils.hasText(triggerRef) - && !StringUtils.hasText(executorRef) - && !StringUtils.hasText(cron) - && !StringUtils.hasText(fixedDelayValue) - && !StringUtils.hasText(fixedRateValue) - && !StringUtils.hasText(maxMessagesPerPollValue), // NOSONAR boolean complexity - "The '@Poller' 'ref' attribute is mutually exclusive with other attributes."); - pollerMetadata = this.beanFactory.getBean(ref, PollerMetadata.class); - } - else { - pollerMetadata = configurePoller(pollingEndpoint, triggerRef, executorRef, fixedDelayValue, - fixedRateValue, maxMessagesPerPollValue, cron, errorChannel, receiveTimeout); - } - } - else { - pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory); - Assert.notNull(pollerMetadata, "No poller has been defined for Annotation-based endpoint, " + - "and no default poller is available within the context."); - } - pollingEndpoint.setTaskExecutor(pollerMetadata.getTaskExecutor()); - pollingEndpoint.setTrigger(pollerMetadata.getTrigger()); - pollingEndpoint.setAdviceChain(pollerMetadata.getAdviceChain()); - long maxMessagesPerPoll = pollerMetadata.getMaxMessagesPerPoll(); - if (maxMessagesPerPoll == PollerMetadata.MAX_MESSAGES_UNBOUNDED && - pollingEndpoint instanceof SourcePollingChannelAdapter) { - // the default is 1 since a source might return - // a non-null and non-interruptible value every time it is invoked - maxMessagesPerPoll = 1; - } - pollingEndpoint.setMaxMessagesPerPoll(maxMessagesPerPoll); - pollingEndpoint.setErrorHandler(pollerMetadata.getErrorHandler()); - if (pollingEndpoint instanceof PollingConsumer pollingConsumer) { - pollingConsumer.setReceiveTimeout(pollerMetadata.getReceiveTimeout()); - } - pollingEndpoint.setTransactionSynchronizationFactory(pollerMetadata.getTransactionSynchronizationFactory()); - } - - private PollerMetadata configurePoller(AbstractPollingEndpoint pollingEndpoint, String triggerRef, - String executorRef, @Nullable String fixedDelayValue, @Nullable String fixedRateValue, - @Nullable String maxMessagesPerPollValue, @Nullable String cron, @Nullable String errorChannel, - @Nullable String receiveTimeout) { - - PollerMetadata pollerMetadata; - pollerMetadata = new PollerMetadata(); - if (StringUtils.hasText(maxMessagesPerPollValue)) { - pollerMetadata.setMaxMessagesPerPoll(Long.parseLong(maxMessagesPerPollValue)); - } - else if (pollingEndpoint instanceof SourcePollingChannelAdapter) { - // SPCAs default to 1 message per poll - pollerMetadata.setMaxMessagesPerPoll(1); - } - - if (StringUtils.hasText(executorRef)) { - pollerMetadata.setTaskExecutor(this.beanFactory.getBean(executorRef, TaskExecutor.class)); - } - - trigger(triggerRef, fixedDelayValue, fixedRateValue, cron, pollerMetadata); - - if (StringUtils.hasText(errorChannel)) { - MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler(); - errorHandler.setDefaultErrorChannelName(errorChannel); - errorHandler.setBeanFactory(this.beanFactory); - pollerMetadata.setErrorHandler(errorHandler); - } - - if (StringUtils.hasText(receiveTimeout)) { - pollerMetadata.setReceiveTimeout(Long.parseLong(receiveTimeout)); - } - return pollerMetadata; - } - - private void trigger(String triggerRef, @Nullable String fixedDelayValue, @Nullable String fixedRateValue, @Nullable String cron, - PollerMetadata pollerMetadata) { - - Trigger trigger = null; - if (StringUtils.hasText(triggerRef)) { - Assert.state(!StringUtils.hasText(cron) && !StringUtils.hasText(fixedDelayValue) - && !StringUtils.hasText(fixedRateValue), - "The '@Poller' 'trigger' attribute is mutually exclusive with other attributes."); - trigger = this.beanFactory.getBean(triggerRef, Trigger.class); - } - else if (StringUtils.hasText(cron)) { - Assert.state(!StringUtils.hasText(fixedDelayValue) && !StringUtils.hasText(fixedRateValue), - "The '@Poller' 'cron' attribute is mutually exclusive with other attributes."); - trigger = new CronTrigger(cron); - } - else if (StringUtils.hasText(fixedDelayValue) || StringUtils.hasText(fixedRateValue)) { - PeriodicTriggerFactoryBean periodicTriggerFactoryBean = new PeriodicTriggerFactoryBean(); - periodicTriggerFactoryBean.setFixedDelayValue(fixedDelayValue); - periodicTriggerFactoryBean.setFixedRateValue(fixedRateValue); - trigger = periodicTriggerFactoryBean.getObject(); - } - //'Trigger' can be null. 'PollingConsumer' does fallback to the 'new PeriodicTrigger(10)'. - pollerMetadata.setTrigger(trigger); - } - - protected String generateHandlerBeanName(String originalBeanName, Method method) { - return generateHandlerBeanName(originalBeanName, MergedAnnotations.from(method), method.getName()); - } - - protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations) { - return generateHandlerBeanName(originalBeanName, mergedAnnotations, null); - } - - protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations, - @Nullable String methodName) { - - String name = MessagingAnnotationUtils.endpointIdValue(mergedAnnotations); - if (!StringUtils.hasText(name)) { - String baseName = originalBeanName + (methodName != null ? "." + methodName : "") + "." - + org.springframework.util.ClassUtils.getShortNameAsProperty(this.annotationType); - name = baseName; - int count = 1; - while (this.beanFactory.containsBean(name)) { - name = baseName + '#' + (++count); - } - } - return name + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX; - } - - protected static void setOutputChannelIfPresent(List annotations, - AbstractMessageProducingHandler handler) { - - String outputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class); - if (StringUtils.hasText(outputChannel)) { - handler.setOutputChannelName(outputChannel); - } - } - - protected void checkMessageHandlerAttributes(String handlerBeanName, List annotations) { - for (String attribute : this.messageHandlerAttributes) { - for (Annotation annotation : annotations) { - Object value = AnnotationUtils.getValue(annotation, attribute); - if (MessagingAnnotationUtils.hasValue(value)) { - throw new BeanDefinitionValidationException("The MessageHandler [" + handlerBeanName + - "] can not be populated because of ambiguity with annotation attributes " + - this.messageHandlerAttributes + " which are not allowed when an integration annotation " + - "is used with a @Bean definition for a MessageHandler." + - "\nThe attribute causing the ambiguity is: [" + attribute + "]." + - "\nUse the appropriate setter on the MessageHandler directly when configuring an " + - "endpoint this way."); - } - } - } - } - - protected boolean resolveAttributeToBoolean(String attribute) { - return Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(attribute)); - } - - protected static @Nullable BeanDefinition buildLambdaMessageProcessor(ResolvableType beanType, - AnnotatedBeanDefinition beanDefinition) { - - Class beanClass = beanType.toClass(); - - if (Function.class.isAssignableFrom(beanClass) - || Consumer.class.isAssignableFrom(beanClass) - || ClassUtils.isKotlinFunction1(beanClass)) { - - Class expectedPayloadType = beanType.getGeneric(0).toClass(); - return BeanDefinitionBuilder.genericBeanDefinition(LambdaMessageProcessor.class) - .addConstructorArgValue(beanDefinition) - .addConstructorArgValue(expectedPayloadType) - .getBeanDefinition(); - } - - return null; - } - - private static void orderable(Method method, MessageHandler handler) { - if (handler instanceof Orderable orderable) { - Order orderAnnotation = AnnotationUtils.findAnnotation(method, Order.class); - if (orderAnnotation != null) { - orderable.setOrder(orderAnnotation.value()); - } - } - } - - /** - * Subclasses must implement this method to create the MessageHandler. - * @param bean The bean. - * @param method The method. - * @param annotations The messaging annotation (or meta-annotation hierarchy) on the method. - * @return The MessageHandler. - */ - protected abstract MessageHandler createHandler(Object bean, Method method, List annotations); - - protected record BeanDefinitionPropertiesMapper(BeanDefinition beanDefinition, List annotations) { - - public BeanDefinitionPropertiesMapper setPropertyValue(String property) { - return setPropertyValue(property, null); - } - - public BeanDefinitionPropertiesMapper setPropertyValue(String property, @Nullable String propertyName) { - String value = MessagingAnnotationUtils.resolveAttribute(this.annotations, property, String.class); - if (StringUtils.hasText(value)) { - this.beanDefinition.getPropertyValues().add(propertyName != null ? propertyName : property, value); - } - return this; - } - - public BeanDefinitionPropertiesMapper setPropertyReference(String property) { - return setPropertyReference(property, null); - } - - public BeanDefinitionPropertiesMapper setPropertyReference(String property, @Nullable String propertyName) { - String value = MessagingAnnotationUtils.resolveAttribute(this.annotations, property, String.class); - if (StringUtils.hasText(value)) { - this.beanDefinition.getPropertyValues() - .add(propertyName != null ? propertyName : property, new RuntimeBeanReference(value)); - } - return this; - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java deleted file mode 100644 index 3cd8baac604..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.aopalliance.aop.Advice; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.AopProxyUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanInitializationException; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.integration.JavaUtils; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.context.Orderable; -import org.springframework.integration.core.MessageProducer; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.support.context.NamedComponent; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.core.DestinationResolver; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * Factory bean to create and configure a {@link MessageHandler}. - * - * @param the target message handler type. - * - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author David Liu - * @author Christian Tzolov - * @author Ngoc Nhan - */ -public abstract class AbstractSimpleMessageHandlerFactoryBean - implements FactoryBean, ApplicationContextAware, BeanFactoryAware, BeanNameAware, - ApplicationEventPublisherAware { - - protected final Log logger = LogFactory.getLog(getClass()); //NOSONAR protected with final - - private final Lock initializationMonitor = new ReentrantLock(); - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - @SuppressWarnings("NullAway.Init") - private H handler; - - private @Nullable MessageChannel outputChannel; - - private @Nullable String outputChannelName; - - private @Nullable Integer order; - - private @Nullable List adviceChain; - - private @Nullable String componentName; - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - private @Nullable DestinationResolver channelResolver; - - private @Nullable Boolean async; - - private boolean initialized; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void setBeanName(String beanName) { - this.beanName = beanName; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - /** - * Set the handler's channel resolver. - * @param channelResolver the channel resolver to set. - */ - public void setChannelResolver(DestinationResolver channelResolver) { - this.channelResolver = channelResolver; - } - - /** - * Set the handler's output channel. - * @param outputChannel the output channel to set. - */ - public void setOutputChannel(MessageChannel outputChannel) { - this.outputChannel = outputChannel; - } - - /** - * Set the handler's output channel name. - * @param outputChannelName the output channel bean name to set. - * @since 5.1.4 - */ - public void setOutputChannelName(String outputChannelName) { - this.outputChannelName = outputChannelName; - } - - /** - * Set the order in which the handler will be subscribed to its channel - * (when subscribable). - * @param order the order to set. - */ - public void setOrder(Integer order) { - this.order = order; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - protected BeanFactory getBeanFactory() { - return this.beanFactory; - } - - /** - * Set the advice chain to be configured within an - * {@link AbstractReplyProducingMessageHandler} to advise just this local endpoint. - * For other handlers, the advice chain is applied around the handler itself. - * @param adviceChain the adviceChain to set. - */ - public void setAdviceChain(List adviceChain) { - this.adviceChain = adviceChain; - } - - /** - * Currently only exposed on the service activator - * namespace. It's not clear that other endpoints would benefit from async support, - * but any subclass of {@link AbstractReplyProducingMessageHandler} can potentially - * return a {@code ListenableFuture}. - * @param async the async to set. - * @since 4.3 - */ - public void setAsync(Boolean async) { - this.async = async; - } - - /** - * Sets the name of the handler component. - * - * @param componentName The component name. - */ - public void setComponentName(String componentName) { - this.componentName = componentName; - } - - @Override - public H getObject() { - if (this.handler == null) { - this.handler = createHandlerInternal(); - } - return this.handler; - } - - protected final H createHandlerInternal() { - this.initializationMonitor.lock(); - try { - Assert.state(!this.initialized, "FactoryBean already initialized"); - this.handler = createHandler(); - JavaUtils.INSTANCE - .acceptIfCondition(this.handler instanceof ApplicationContextAware && this.applicationContext != null, - this.applicationContext, - context -> ((ApplicationContextAware) this.handler).setApplicationContext(this.applicationContext)) - .acceptIfCondition(this.handler instanceof BeanFactoryAware && getBeanFactory() != null, - getBeanFactory(), - factory -> ((BeanFactoryAware) this.handler).setBeanFactory(factory)) - .acceptIfCondition(this.handler instanceof BeanNameAware && this.beanName != null, this.beanName, - name -> ((BeanNameAware) this.handler).setBeanName(this.beanName)) - .acceptIfCondition(this.handler instanceof ApplicationEventPublisherAware - && this.applicationEventPublisher != null, - this.applicationEventPublisher, - publisher -> ((ApplicationEventPublisherAware) this.handler) - .setApplicationEventPublisher(publisher)); - configureOutputChannelIfAny(); - Object actualHandler = extractTarget(this.handler); - if (actualHandler == null) { - actualHandler = this.handler; - } - final Object handlerToConfigure = actualHandler; // must be final for lambdas - integrationObjectSupport(actualHandler, handlerToConfigure); - adviceChain(actualHandler); - JavaUtils.INSTANCE - .acceptIfCondition(this.async != null && actualHandler instanceof AbstractMessageProducingHandler, - this.async, - asyncValue -> ((AbstractMessageProducingHandler) handlerToConfigure).setAsync(asyncValue)) - .acceptIfCondition(this.handler instanceof Orderable && this.order != null, - this.order, theOrder -> ((Orderable) this.handler).setOrder(theOrder)); - this.initialized = true; - } - finally { - this.initializationMonitor.unlock(); - } - initializingBean(); - return this.handler; - } - - private void integrationObjectSupport(Object actualHandler, final Object handlerToConfigure) { - if (actualHandler instanceof IntegrationObjectSupport) { - JavaUtils.INSTANCE - .acceptIfNotNull(this.componentName, - name -> ((IntegrationObjectSupport) handlerToConfigure).setComponentName(name)) - .acceptIfNotNull(this.channelResolver, - resolver -> ((IntegrationObjectSupport) handlerToConfigure).setChannelResolver(resolver)); - } - } - - private void adviceChain(Object actualHandler) { - if (!CollectionUtils.isEmpty(this.adviceChain)) { - if (actualHandler instanceof AbstractReplyProducingMessageHandler abstractReplyProducingMessageHandler) { - abstractReplyProducingMessageHandler.setAdviceChain(this.adviceChain); - } - else if (this.logger.isDebugEnabled()) { - String name = this.componentName; - if (name == null && actualHandler instanceof NamedComponent namedComponent) { - name = namedComponent.getBeanName(); - } - this.logger.debug("adviceChain can only be set on an AbstractReplyProducingMessageHandler" - + (name == null ? "" : (", " + name)) + "."); - } - } - } - - private void initializingBean() { - if (this.handler instanceof InitializingBean initializingBean) { - try { - initializingBean.afterPropertiesSet(); - } - catch (Exception e) { - throw new BeanInitializationException("failed to initialize MessageHandler", e); - } - } - } - - private void configureOutputChannelIfAny() { - if (this.handler instanceof MessageProducer messageProducer) { - if (this.outputChannel != null) { - messageProducer.setOutputChannel(this.outputChannel); - } - else if (this.outputChannelName != null) { - messageProducer.setOutputChannelName(this.outputChannelName); - } - } - } - - protected abstract H createHandler(); - - @Override - public Class getObjectType() { - if (this.handler != null) { - return this.handler.getClass(); - } - return getPreCreationHandlerType(); - } - - /** - * Subclasses can override this to return a more specific type before handler creation. - * After handler creation, the actual type is used. - * @return the type. - */ - protected Class getPreCreationHandlerType() { - return MessageHandler.class; - } - - @Override - public boolean isSingleton() { - return true; - } - - private static Object extractTarget(Object object) { - return Objects.requireNonNullElse(AopProxyUtils.getSingletonTarget(object), object); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java deleted file mode 100644 index 342ca868da0..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Base class for FactoryBeans that create standard MessageHandler instances. - * - * @author Mark Fisher - * @author Alexander Peters - * @author Gary Russell - * @author Artem Bilan - * @author David Liu - * @author Ngoc Nhan - */ -public abstract class AbstractStandardMessageHandlerFactoryBean - extends AbstractSimpleMessageHandlerFactoryBean implements DisposableBean { - - private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - - private static final Set REFERENCED_REPLY_PRODUCERS = new HashSet<>(); - - @SuppressWarnings("NullAway.Init") - private Boolean requiresReply; - - @SuppressWarnings("NullAway.Init") - private Object targetObject; - - @SuppressWarnings("NullAway.Init") - private String targetMethodName; - - @SuppressWarnings("NullAway.Init") - private Expression expression; - - @SuppressWarnings("NullAway.Init") - private Long sendTimeout; - - @SuppressWarnings("NullAway.Init") - private MessageHandler replyHandler; - - /** - * Set the target POJO for the message handler. - * @param targetObject the target object. - */ - public void setTargetObject(Object targetObject) { - this.targetObject = targetObject; - } - - /** - * Set the method name for the message handler. - * @param targetMethodName the target method name. - */ - public void setTargetMethodName(String targetMethodName) { - this.targetMethodName = targetMethodName; - } - - /** - * Set a SpEL expression to use. - * @param expressionString the expression as a String. - */ - public void setExpressionString(String expressionString) { - this.expression = EXPRESSION_PARSER.parseExpression(expressionString); - } - - /** - * Set a SpEL expression to use. - * @param expression the expression. - */ - public void setExpression(Expression expression) { - this.expression = expression; - } - - public void setRequiresReply(Boolean requiresReply) { - this.requiresReply = requiresReply; - } - - public void setSendTimeout(Long sendTimeout) { - this.sendTimeout = sendTimeout; - } - - public Long getSendTimeout() { - return this.sendTimeout; - } - - @Override - public void destroy() { - if (this.replyHandler != null) { - REFERENCED_REPLY_PRODUCERS.remove(this.replyHandler); - } - } - - @Override - protected MessageHandler createHandler() { - MessageHandler handler; - if (this.targetObject == null) { - Assert.isTrue(!StringUtils.hasText(this.targetMethodName), - "The target method is only allowed when a target object (ref or inner bean) is also provided."); - } - if (this.targetObject != null) { - Assert.state(this.expression == null, - "The 'targetObject' and 'expression' properties are mutually exclusive."); - AbstractMessageProducingHandler actualHandler = - IntegrationObjectSupport.extractTypeIfPossible(this.targetObject, - AbstractMessageProducingHandler.class); - boolean targetIsDirectReplyProducingHandler = - actualHandler != null - && canBeUsedDirect(actualHandler) // give subclasses a say - && methodIsHandleMessageOrEmpty(this.targetMethodName); - if (this.targetObject instanceof MessageProcessor) { - handler = createMessageProcessingHandler((MessageProcessor) this.targetObject); - } - else if (targetIsDirectReplyProducingHandler) { - if (logger.isDebugEnabled()) { - logger.debug("Wiring handler (" + this.targetObject + ") directly into endpoint"); - } - checkReuse(Objects.requireNonNull(actualHandler)); - postProcessReplyProducer(Objects.requireNonNull(actualHandler)); - handler = (MessageHandler) this.targetObject; - } - else { - handler = createMethodInvokingHandler(this.targetObject, this.targetMethodName); - } - } - else if (this.expression != null) { - handler = createExpressionEvaluatingHandler(this.expression); - } - else { - handler = createDefaultHandler(); - } - return handler; - } - - protected void checkForIllegalTarget(Object targetObject, @Nullable String targetMethodName) { - if (targetObject instanceof AbstractReplyProducingMessageHandler - && methodIsHandleMessageOrEmpty(targetMethodName)) { - /* - * If we allow an ARPMH to be the target of another ARPMH, the reply would - * be attempted to be sent by the inner (no output channel) and a reply would - * never be received by the outer (fails if replyRequired). - */ - throw new IllegalArgumentException("AbstractReplyProducingMessageHandler.handleMessage() " - + "is not allowed for a MethodInvokingHandler"); - } - } - - private void checkReuse(AbstractMessageProducingHandler replyHandler) { - Assert.isTrue(!REFERENCED_REPLY_PRODUCERS.contains(replyHandler), - "An AbstractMessageProducingMessageHandler may only be referenced once (" + - replyHandler.getBeanName() + ") - use scope=\"prototype\""); - REFERENCED_REPLY_PRODUCERS.add(replyHandler); - this.replyHandler = replyHandler; - } - - /** - * Subclasses must implement this method to create the MessageHandler. - * @param targetObject the object to use for method invocation. - * @param targetMethodName the method name of the target object to invoke. - * @return the method invoking {@link MessageHandler} implementation. - */ - protected abstract MessageHandler createMethodInvokingHandler(Object targetObject, @Nullable String targetMethodName); - - protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - throw new UnsupportedOperationException(getClass().getName() + " does not support expressions."); - } - - protected MessageHandler createMessageProcessingHandler(MessageProcessor processor) { - return createMethodInvokingHandler(processor, null); - } - - protected MessageHandler createDefaultHandler() { - throw new IllegalArgumentException("Exactly one of the 'targetObject' or 'expression' property is required."); - } - - protected boolean methodIsHandleMessageOrEmpty(@Nullable String targetMethodName) { - return (!StringUtils.hasText(targetMethodName) - || "handleMessage".equals(targetMethodName)); - } - - protected boolean canBeUsedDirect(AbstractMessageProducingHandler handler) { - return false; - } - - protected void postProcessReplyProducer(AbstractMessageProducingHandler handler) { - if (this.sendTimeout != null) { - handler.setSendTimeout(this.sendTimeout); - } - - if (this.requiresReply != null) { - if (handler instanceof AbstractReplyProducingMessageHandler abstractReplyProducingMessageHandler) { - abstractReplyProducingMessageHandler.setRequiresReply(this.requiresReply); - } - else { - if (this.requiresReply && logger.isDebugEnabled()) { - logger.debug("requires-reply can only be set to AbstractReplyProducingMessageHandler " + - "or its subclass, " + handler.getBeanName() + " doesn't support it."); - } - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AggregatorAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AggregatorAnnotationPostProcessor.java deleted file mode 100644 index 4e6b5c579fc..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AggregatorAnnotationPostProcessor.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; - -import org.springframework.integration.aggregator.AggregatingMessageHandler; -import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy; -import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor; -import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy; -import org.springframework.integration.annotation.Aggregator; -import org.springframework.integration.annotation.CorrelationStrategy; -import org.springframework.integration.annotation.ReleaseStrategy; -import org.springframework.integration.store.SimpleMessageStore; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.StringUtils; - -/** - * Post-processor for the {@link Aggregator @Aggregator} annotation. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method); - - MethodInvokingReleaseStrategy releaseStrategy = null; - Method releaseStrategyMethod = MessagingAnnotationUtils.findAnnotatedMethod(bean, ReleaseStrategy.class); - if (releaseStrategyMethod != null) { - releaseStrategy = new MethodInvokingReleaseStrategy(bean, releaseStrategyMethod); - } - - MethodInvokingCorrelationStrategy correlationStrategy = null; - Method correlationStrategyMethod = - MessagingAnnotationUtils.findAnnotatedMethod(bean, CorrelationStrategy.class); - if (correlationStrategyMethod != null) { - correlationStrategy = new MethodInvokingCorrelationStrategy(bean, correlationStrategyMethod); - } - - AggregatingMessageHandler handler = new AggregatingMessageHandler(processor, new SimpleMessageStore(), - correlationStrategy, releaseStrategy); - - String discardChannelName = - MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class); - if (StringUtils.hasText(discardChannelName)) { - handler.setDiscardChannelName(discardChannelName); - } - String outputChannelName = - MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class); - if (StringUtils.hasText(outputChannelName)) { - handler.setOutputChannelName(outputChannelName); - } - String sendPartialResultsOnExpiry = MessagingAnnotationUtils.resolveAttribute(annotations, - "sendPartialResultsOnExpiry", String.class); - if (sendPartialResultsOnExpiry != null) { - handler.setSendPartialResultOnExpiry(resolveAttributeToBoolean(sendPartialResultsOnExpiry)); - } - return handler; - } - - @Override - public boolean beanAnnotationAware() { - return false; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AggregatorFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AggregatorFactoryBean.java deleted file mode 100644 index 99188c3d15d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AggregatorFactoryBean.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; - -import org.aopalliance.aop.Advice; -import org.jetbrains.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.JavaUtils; -import org.springframework.integration.aggregator.AbstractAggregatingMessageGroupProcessor; -import org.springframework.integration.aggregator.AggregatingMessageHandler; -import org.springframework.integration.aggregator.CorrelationStrategy; -import org.springframework.integration.aggregator.DelegatingMessageGroupProcessor; -import org.springframework.integration.aggregator.MessageGroupProcessor; -import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor; -import org.springframework.integration.aggregator.ReleaseStrategy; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.support.locks.LockRegistry; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.util.StringUtils; - -/** - * {@link org.springframework.beans.factory.FactoryBean} to create an - * {@link AggregatingMessageHandler}. - * - * @author Gary Russell - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 4.2 - * - */ -public class AggregatorFactoryBean extends AbstractSimpleMessageHandlerFactoryBean { - - @SuppressWarnings("NullAway.Init") - private Object processorBean; - - private @Nullable String methodName; - - private @Nullable Boolean expireGroupsUponCompletion; - - private @Nullable Long sendTimeout; - - private @Nullable String outputChannelName; - - private @Nullable LockRegistry lockRegistry; - - private @Nullable MessageGroupStore messageStore; - - private @Nullable CorrelationStrategy correlationStrategy; - - private @Nullable ReleaseStrategy releaseStrategy; - - private @Nullable Expression groupTimeoutExpression; - - private @Nullable List forceReleaseAdviceChain; - - private @Nullable TaskScheduler taskScheduler; - - private @Nullable MessageChannel discardChannel; - - private @Nullable String discardChannelName; - - private @Nullable Boolean sendPartialResultOnExpiry; - - private @Nullable Boolean discardIndividuallyOnExpiry; - - private @Nullable Long minimumTimeoutForEmptyGroups; - - private @Nullable Boolean expireGroupsUponTimeout; - - private @Nullable Boolean popSequence; - - private @Nullable Boolean releaseLockBeforeSend; - - private @Nullable Long expireTimeout; - - private @Nullable Long expireDuration; - - private @Nullable Function> headersFunction; - - private @Nullable BiFunction, String, String> groupConditionSupplier; - - public void setProcessorBean(Object processorBean) { - this.processorBean = processorBean; - } - - public void setMethodName(String methodName) { - this.methodName = methodName; - } - - public void setExpireGroupsUponCompletion(Boolean expireGroupsUponCompletion) { - this.expireGroupsUponCompletion = expireGroupsUponCompletion; - } - - public void setSendTimeout(Long sendTimeout) { - this.sendTimeout = sendTimeout; - } - - @Override - public void setOutputChannelName(String outputChannelName) { - this.outputChannelName = outputChannelName; - } - - public void setLockRegistry(LockRegistry lockRegistry) { - this.lockRegistry = lockRegistry; - } - - public void setMessageStore(MessageGroupStore messageStore) { - this.messageStore = messageStore; - } - - public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) { - this.correlationStrategy = correlationStrategy; - } - - public void setReleaseStrategy(ReleaseStrategy releaseStrategy) { - this.releaseStrategy = releaseStrategy; - } - - public void setGroupTimeoutExpression(Expression groupTimeoutExpression) { - this.groupTimeoutExpression = groupTimeoutExpression; - } - - public void setForceReleaseAdviceChain(List forceReleaseAdviceChain) { - this.forceReleaseAdviceChain = forceReleaseAdviceChain; - } - - public void setTaskScheduler(TaskScheduler taskScheduler) { - this.taskScheduler = taskScheduler; - } - - public void setDiscardChannel(MessageChannel discardChannel) { - this.discardChannel = discardChannel; - } - - public void setDiscardChannelName(String discardChannelName) { - this.discardChannelName = discardChannelName; - } - - public void setSendPartialResultOnExpiry(Boolean sendPartialResultOnExpiry) { - this.sendPartialResultOnExpiry = sendPartialResultOnExpiry; - } - - public void setMinimumTimeoutForEmptyGroups(Long minimumTimeoutForEmptyGroups) { - this.minimumTimeoutForEmptyGroups = minimumTimeoutForEmptyGroups; - } - - public void setExpireGroupsUponTimeout(Boolean expireGroupsUponTimeout) { - this.expireGroupsUponTimeout = expireGroupsUponTimeout; - } - - public void setPopSequence(Boolean popSequence) { - this.popSequence = popSequence; - } - - public void setReleaseLockBeforeSend(Boolean releaseLockBeforeSend) { - this.releaseLockBeforeSend = releaseLockBeforeSend; - } - - public void setHeadersFunction(Function> headersFunction) { - this.headersFunction = headersFunction; - } - - public void setExpireTimeout(Long expireTimeout) { - this.expireTimeout = expireTimeout; - } - - public void setExpireDurationMillis(Long expireDuration) { - this.expireDuration = expireDuration; - } - - public void setGroupConditionSupplier(BiFunction, String, String> groupConditionSupplier) { - this.groupConditionSupplier = groupConditionSupplier; - } - - /** - * Set to {@code false} to send to discard channel a whole expired group as a single message. - * @param discardIndividuallyOnExpiry false to discard the whole group as one message. - * @since 6.5 - * @see org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler#setDiscardIndividuallyOnExpiry(boolean) - */ - public void setDiscardIndividuallyOnExpiry(Boolean discardIndividuallyOnExpiry) { - this.discardIndividuallyOnExpiry = discardIndividuallyOnExpiry; - } - - @Override - protected AggregatingMessageHandler createHandler() { - MessageGroupProcessor outputProcessor; - if (this.processorBean instanceof MessageGroupProcessor messageGroupProcessor) { - outputProcessor = messageGroupProcessor; - } - else { - if (!StringUtils.hasText(this.methodName)) { - outputProcessor = new MethodInvokingMessageGroupProcessor(this.processorBean); - } - else { - outputProcessor = new MethodInvokingMessageGroupProcessor(this.processorBean, this.methodName); - } - } - - if (this.headersFunction != null) { - if (outputProcessor instanceof AbstractAggregatingMessageGroupProcessor abstractAggregatingMessageGroupProcessor) { - abstractAggregatingMessageGroupProcessor.setHeadersFunction(this.headersFunction); - } - else { - outputProcessor = new DelegatingMessageGroupProcessor(outputProcessor, this.headersFunction); - } - } - - AggregatingMessageHandler aggregator = new AggregatingMessageHandler(outputProcessor); - - JavaUtils.INSTANCE - .acceptIfNotNull(this.expireGroupsUponCompletion, aggregator::setExpireGroupsUponCompletion) - .acceptIfNotNull(this.sendTimeout, aggregator::setSendTimeout) - .acceptIfNotNull(this.outputChannelName, aggregator::setOutputChannelName) - .acceptIfNotNull(this.lockRegistry, aggregator::setLockRegistry) - .acceptIfNotNull(this.messageStore, aggregator::setMessageStore) - .acceptIfNotNull(obtainCorrelationStrategy(), aggregator::setCorrelationStrategy) - .acceptIfNotNull(obtainReleaseStrategy(), aggregator::setReleaseStrategy) - .acceptIfNotNull(this.groupTimeoutExpression, aggregator::setGroupTimeoutExpression) - .acceptIfNotNull(this.forceReleaseAdviceChain, aggregator::setForceReleaseAdviceChain) - .acceptIfNotNull(this.taskScheduler, aggregator::setTaskScheduler) - .acceptIfNotNull(this.discardChannel, aggregator::setDiscardChannel) - .acceptIfNotNull(this.discardChannelName, aggregator::setDiscardChannelName) - .acceptIfNotNull(this.sendPartialResultOnExpiry, aggregator::setSendPartialResultOnExpiry) - .acceptIfNotNull(this.minimumTimeoutForEmptyGroups, aggregator::setMinimumTimeoutForEmptyGroups) - .acceptIfNotNull(this.expireGroupsUponTimeout, aggregator::setExpireGroupsUponTimeout) - .acceptIfNotNull(this.popSequence, aggregator::setPopSequence) - .acceptIfNotNull(this.releaseLockBeforeSend, aggregator::setReleaseLockBeforeSend) - .acceptIfNotNull(this.expireDuration, - (duration) -> aggregator.setExpireDuration(Duration.ofMillis(duration))) - .acceptIfNotNull(this.groupConditionSupplier, aggregator::setGroupConditionSupplier) - .acceptIfNotNull(this.expireTimeout, aggregator::setExpireTimeout) - .acceptIfNotNull(this.discardIndividuallyOnExpiry, aggregator::setDiscardIndividuallyOnExpiry); - - return aggregator; - } - - @Nullable - private CorrelationStrategy obtainCorrelationStrategy() { - if (this.correlationStrategy == null && this.processorBean != null) { - CorrelationStrategyFactoryBean correlationStrategyFactoryBean = new CorrelationStrategyFactoryBean(); - correlationStrategyFactoryBean.setTarget(this.processorBean); - correlationStrategyFactoryBean.afterPropertiesSet(); - return correlationStrategyFactoryBean.getObject(); - } - return this.correlationStrategy; - } - - @Nullable - private ReleaseStrategy obtainReleaseStrategy() { - if (this.releaseStrategy == null && this.processorBean != null) { - ReleaseStrategyFactoryBean releaseStrategyFactoryBean = new ReleaseStrategyFactoryBean(); - releaseStrategyFactoryBean.setTarget(this.processorBean); - releaseStrategyFactoryBean.afterPropertiesSet(); - return releaseStrategyFactoryBean.getObject(); - } - return this.releaseStrategy; - } - - @Override - protected Class getPreCreationHandlerType() { - return AggregatingMessageHandler.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ApplicationRunningController.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ApplicationRunningController.java deleted file mode 100644 index d836f7b4025..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ApplicationRunningController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2025-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.springframework.context.SmartLifecycle; - -/** - * An infrastructure bean to hold the status of the application context when - * it is ready for interaction: refreshed or started. - *

- * Well-known {@link org.springframework.context.ConfigurableApplicationContext#isRunning()} - * (or {@link org.springframework.context.event.ContextRefreshedEvent}) - * is good for target applications, when all the beans are already started, - * but most of Spring Integration channel adapters initiate their logic - * from the {@link SmartLifecycle#start()} implementation, so it would be false report - * that application is not running during start. - *

- * This implementation uses {@value Integer#MIN_VALUE} for its phase to be started as early as possible. - * - * @author Artem Bilan - * - * @since 6.5 - */ -class ApplicationRunningController implements SmartLifecycle { - - private volatile boolean running; - - @Override - public void start() { - this.running = true; - } - - @Override - public void stop() { - this.running = false; - } - - @Override - public boolean isRunning() { - return this.running; - } - - @Override - public int getPhase() { - return Integer.MIN_VALUE; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/BridgeFromAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/BridgeFromAnnotationPostProcessor.java deleted file mode 100644 index ce55298798d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/BridgeFromAnnotationPostProcessor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; - -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.integration.annotation.BridgeFrom; -import org.springframework.integration.annotation.BridgeTo; -import org.springframework.integration.handler.BridgeHandler; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; - -/** - * Post-processor for the {@link BridgeFrom @BridgeFrom} annotation. - * - * @author Artem Bilan - * - * @since 4.0 - */ -public class BridgeFromAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - @Override - public String getInputChannelAttribute() { - return AnnotationUtils.VALUE; - } - - @Override - public boolean supportsPojoMethod() { - return false; - } - - @Override - public boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List annotations) { - Assert.isTrue(super.shouldCreateEndpoint(mergedAnnotations, annotations), - "'@BridgeFrom.value()' (inputChannelName) must not be empty"); - Assert.isTrue(!mergedAnnotations.isPresent(BridgeTo.class), - "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' '@Bean' method annotations"); - return true; - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotationChain) { - - return BeanDefinitionBuilder.genericBeanDefinition(BridgeHandler.class) - .addPropertyReference("outputChannel", beanName) - .getBeanDefinition(); - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - throw new UnsupportedOperationException(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/BridgeToAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/BridgeToAnnotationPostProcessor.java deleted file mode 100644 index 4a321b168a6..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/BridgeToAnnotationPostProcessor.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; - -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.ComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.integration.annotation.BridgeFrom; -import org.springframework.integration.annotation.BridgeTo; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.handler.BridgeHandler; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Post-processor for the {@link BridgeTo @BridgeTo} annotation. - * - * @author Artem Bilan - * - * @since 4.0 - */ -public class BridgeToAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - @Override - public boolean supportsPojoMethod() { - return false; - } - - @Override - public boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List annotations) { - Assert.isTrue(!mergedAnnotations.isPresent(BridgeFrom.class), - "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' '@Bean' method annotations"); - return true; - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotationChain) { - - GenericBeanDefinition bridgeHandlerBeanDefinition = new GenericBeanDefinition(); - bridgeHandlerBeanDefinition.setBeanClass(BridgeHandler.class); - String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotationChain, "value", String.class); - if (StringUtils.hasText(outputChannelName)) { - bridgeHandlerBeanDefinition.getPropertyValues() - .addPropertyValue("outputChannel", new RuntimeBeanReference(outputChannelName)); - } - return bridgeHandlerBeanDefinition; - } - - @Override - protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition, - ComponentDefinition beanDefinition, List annotations) { - - return BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class) - .addPropertyReference("handler", handlerBeanDefinition.getName()) - .addPropertyReference("inputChannel", beanDefinition.getName()) - .getBeanDefinition(); - } - - @Override - protected AbstractEndpoint createEndpoint(MessageHandler handler, Method method, List annotations) { - throw new UnsupportedOperationException(); - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - throw new UnsupportedOperationException(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelInitializer.java deleted file mode 100644 index a6223943269..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelInitializer.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Collection; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.util.Assert; - -/** - * An {@link InitializingBean} implementation that is responsible for creating - * channels that are not explicitly defined but identified via the 'input-channel' - * attribute of the corresponding endpoints. - *

- * This bean plays a role of pre-instantiator since it is instantiated and - * initialized as the very first bean of all Spring Integration beans using - * {@link org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler}. - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1.1 - */ -public final class ChannelInitializer implements BeanFactoryAware, InitializingBean { - - private static final Log LOGGER = LogFactory.getLog(ChannelInitializer.class); - - @SuppressWarnings("NullAway.Init") - private volatile DefaultListableBeanFactory beanFactory; - - private volatile boolean autoCreate = true; - - ChannelInitializer() { - } - - public void setAutoCreate(boolean autoCreate) { - this.autoCreate = autoCreate; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = (DefaultListableBeanFactory) beanFactory; - } - - @Override - public void afterPropertiesSet() { - Assert.notNull(this.beanFactory, "'beanFactory' must not be null"); - if (this.autoCreate) { - AutoCreateCandidatesCollector channelCandidatesCollector = - this.beanFactory.getBean(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME, - AutoCreateCandidatesCollector.class); - // at this point channelNames are all resolved with placeholders and SpEL - Collection channelNames = channelCandidatesCollector.channelNames; - if (channelNames != null) { - for (String channelName : channelNames) { - if (!this.beanFactory.containsBean(channelName)) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Auto-creating channel '" + channelName + "' as DirectChannel"); - } - DirectChannel channelToRegister = new DirectChannel(); - this.beanFactory.registerSingleton(channelName, channelToRegister); - this.beanFactory.initializeBean(channelToRegister, channelName); - } - } - } - } - } - - /** - * Collects candidate channel names to be auto-created by {@link ChannelInitializer}. - * @param channelNames the auto-create candidate channel bean names. - */ - public record AutoCreateCandidatesCollector(Collection channelNames) { - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java deleted file mode 100644 index 8e960a37a5e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ConsumerEndpointFactoryBean.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; - -import org.aopalliance.aop.Advice; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.support.AopUtils; -import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; -import org.springframework.aot.hint.annotation.Reflective; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.SmartLifecycle; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.channel.FixedSubscriberChannel; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.endpoint.PollingConsumer; -import org.springframework.integration.endpoint.ReactiveStreamsConsumer; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; -import org.springframework.integration.handler.advice.HandleMessageAdvice; -import org.springframework.integration.scheduling.PollerMetadata; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.ReactiveMessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.core.DestinationResolver; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * The {@link FactoryBean} implementation for {@link AbstractEndpoint} population. - * Controls all the necessary properties and lifecycle. - * According the provided {@link MessageChannel} implementation populates - * a {@link PollingConsumer} for the {@link PollableChannel}, - * an {@link EventDrivenConsumer} for the {@link SubscribableChannel} - * and {@link ReactiveStreamsConsumer} for all other channel implementations. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Josh Long - * @author Gary Russell - * @author Artem Bilan - * @author Christian Tzolov - */ -public class ConsumerEndpointFactoryBean - implements FactoryBean, BeanFactoryAware, BeanNameAware, BeanClassLoaderAware, - InitializingBean, SmartLifecycle, DisposableBean { - - private static final LogAccessor LOGGER = new LogAccessor(LogFactory.getLog(ConsumerEndpointFactoryBean.class)); - - private final Lock initializationMonitor = new ReentrantLock(); - - private final Lock handlerMonitor = new ReentrantLock(); - - @SuppressWarnings("NullAway.Init") - private MessageHandler handler; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - @SuppressWarnings("NullAway.Init") - private String inputChannelName; - - @Nullable - private PollerMetadata pollerMetadata; - - @Nullable - private Function>, ? extends Publisher>> reactiveCustomizer; - - @Nullable - private Boolean autoStartup; - - private int phase = 0; - - private boolean isPhaseSet; - - @Nullable - private String role; - - @Nullable - private MessageChannel inputChannel; - - @SuppressWarnings("NullAway.Init") - private ConfigurableBeanFactory beanFactory; - - @SuppressWarnings("NullAway.Init") - private ClassLoader beanClassLoader; - - @Nullable - private List adviceChain; - - @SuppressWarnings("NullAway.Init") - private DestinationResolver channelResolver; - - @Nullable - private TaskScheduler taskScheduler; - - @SuppressWarnings("NullAway.Init") - private volatile AbstractEndpoint endpoint; - - private volatile boolean initialized; - - @Reflective // The native image doesn't see this method because its type is not specific - public void setHandler(Object handler) { - Assert.isTrue(handler instanceof MessageHandler || handler instanceof ReactiveMessageHandler, - "'handler' must be an instance of 'MessageHandler' or 'ReactiveMessageHandler'"); - this.handlerMonitor.lock(); - try { - Assert.isNull(this.handler, "handler cannot be overridden"); - if (handler instanceof ReactiveMessageHandler) { - this.handler = new ReactiveMessageHandlerAdapter((ReactiveMessageHandler) handler); - } - else { - this.handler = (MessageHandler) handler; - } - } - finally { - this.handlerMonitor.unlock(); - } - } - - public MessageHandler getHandler() { - return this.handler; - } - - public void setInputChannel(MessageChannel inputChannel) { - this.inputChannel = inputChannel; - } - - public void setInputChannelName(String inputChannelName) { - this.inputChannelName = inputChannelName; - } - - public void setPollerMetadata(PollerMetadata pollerMetadata) { - this.pollerMetadata = pollerMetadata; - } - - public void setReactiveCustomizer( - @Nullable Function>, ? extends Publisher>> reactiveCustomizer) { - - this.reactiveCustomizer = reactiveCustomizer; - } - - /** - * Specify the {@link DestinationResolver} strategy to use. - * The default is a BeanFactoryChannelResolver. - * @param channelResolver The channel resolver. - * @since 4.1.3 - */ - public void setChannelResolver(DestinationResolver channelResolver) { - Assert.notNull(channelResolver, "'channelResolver' must not be null"); - this.channelResolver = channelResolver; - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } - - public void setAutoStartup(Boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public void setPhase(int phase) { - this.phase = phase; - this.isPhaseSet = true; - } - - public void setRole(String role) { - this.role = role; - } - - @Override - public void setBeanName(String beanName) { - this.beanName = beanName; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory, "a ConfigurableBeanFactory is required"); - this.beanFactory = (ConfigurableBeanFactory) beanFactory; - } - - public void setAdviceChain(List adviceChain) { - Assert.notNull(adviceChain, "adviceChain must not be null"); - this.adviceChain = adviceChain; - } - - public void setTaskScheduler(TaskScheduler taskScheduler) { - this.taskScheduler = taskScheduler; - } - - @Override - public void afterPropertiesSet() { - if (this.beanName == null) { - LOGGER.error(() -> "The MessageHandler [" + this.handler + "] will be created without a 'componentName'. " + - "Consider specifying the 'beanName' property on this ConsumerEndpointFactoryBean."); - } - else { - populateComponentNameIfAny(); - } - - if (!(this.handler instanceof ReactiveMessageHandlerAdapter)) { - this.handler = adviceChain(this.handler); - } - else if (!CollectionUtils.isEmpty(this.adviceChain)) { - this.handler = - new ReactiveMessageHandlerAdapter( - adviceChain(((ReactiveMessageHandlerAdapter) this.handler).getDelegate())); - } - if (this.channelResolver == null) { - this.channelResolver = ChannelResolverUtils.getChannelResolver(this.beanFactory); - } - initializeEndpoint(); - } - - private void populateComponentNameIfAny() { - try { - if (!this.beanName.startsWith("org.springframework")) { - MessageHandler targetHandler = this.handler; - if (AopUtils.isAopProxy(targetHandler)) { - Object target = ((Advised) targetHandler).getTargetSource().getTarget(); - if (target instanceof MessageHandler) { - targetHandler = (MessageHandler) target; - } - } - if (targetHandler instanceof IntegrationObjectSupport) { - ((IntegrationObjectSupport) targetHandler).setComponentName(this.beanName); - } - } - } - catch (Exception ex) { - LOGGER.debug(() -> "Could not set component name for handler " - + this.handler + " for " + this.beanName + " :" + ex.getMessage()); - } - } - - @SuppressWarnings("unchecked") - private H adviceChain(H handler) { - H theHandler = handler; - if (!CollectionUtils.isEmpty(this.adviceChain)) { - /* - * ARPMHs advise the handleRequestMessage method internally and already have the advice chain injected. - * So we only advise handlers that are not reply-producing. - * Or if one (or more) of advices is IdempotentReceiverInterceptor. - * If the handler is already advised, - * add the configured advices to its chain, otherwise create a proxy. - */ - Class targetClass = AopUtils.getTargetClass(theHandler); - boolean replyMessageHandler = AbstractReplyProducingMessageHandler.class.isAssignableFrom(targetClass); - - for (Advice advice : this.adviceChain) { - if (!replyMessageHandler || advice instanceof HandleMessageAdvice) { - NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(advice); - handlerAdvice.addMethodName("handleMessage"); - if (theHandler instanceof Advised) { - ((Advised) theHandler).addAdvisor(handlerAdvice); - } - else { - ProxyFactory proxyFactory = new ProxyFactory(theHandler); - proxyFactory.addAdvisor(handlerAdvice); - theHandler = (H) proxyFactory.getProxy(this.beanClassLoader); - } - } - } - } - return theHandler; - } - - @Override - public AbstractEndpoint getObject() { - if (!this.initialized) { - this.initializeEndpoint(); - } - return this.endpoint; - } - - @Override - public Class getObjectType() { - if (this.endpoint == null) { - return AbstractEndpoint.class; - } - return this.endpoint.getClass(); - } - - private void initializeEndpoint() { - this.initializationMonitor.lock(); - try { - if (this.initialized) { - return; - } - MessageChannel channel = resolveInputChannel(); - - Assert.state(this.reactiveCustomizer == null || this.pollerMetadata == null, - "The 'pollerMetadata' and 'reactiveCustomizer' are mutually exclusive."); - - if (channel instanceof Publisher || - this.handler instanceof ReactiveMessageHandlerAdapter || - this.reactiveCustomizer != null) { - - reactiveStreamsConsumer(channel); - } - else if (channel instanceof SubscribableChannel) { - eventDrivenConsumer(channel); - } - else if (channel instanceof PollableChannel) { - pollingConsumer(channel); - } - else { - throw new IllegalArgumentException("Unsupported 'inputChannel' type: '" - + channel.getClass().getName() + "'. " + - "Must be one of 'SubscribableChannel', 'PollableChannel' " + - "or 'ReactiveStreamsSubscribableChannel'"); - } - this.endpoint.setBeanName(this.beanName); - this.endpoint.setBeanFactory(this.beanFactory); - smartLifecycle(); - this.endpoint.setRole(this.role); - if (this.taskScheduler != null) { - this.endpoint.setTaskScheduler(this.taskScheduler); - } - this.endpoint.afterPropertiesSet(); - this.initialized = true; - } - finally { - this.initializationMonitor.unlock(); - } - } - - private MessageChannel resolveInputChannel() { - MessageChannel channel = null; - if (StringUtils.hasText(this.inputChannelName)) { - channel = this.channelResolver.resolveDestination(this.inputChannelName); - } - if (this.inputChannel != null) { - channel = this.inputChannel; - } - Assert.state(channel != null, "one of inputChannelName or inputChannel is required"); - return channel; - } - - private void reactiveStreamsConsumer(MessageChannel channel) { - ReactiveStreamsConsumer reactiveStreamsConsumer; - if (this.handler instanceof ReactiveMessageHandlerAdapter) { - reactiveStreamsConsumer = new ReactiveStreamsConsumer(channel, - ((ReactiveMessageHandlerAdapter) this.handler).getDelegate()); - } - else { - reactiveStreamsConsumer = new ReactiveStreamsConsumer(channel, this.handler); - } - - reactiveStreamsConsumer.setReactiveCustomizer(this.reactiveCustomizer); - - this.endpoint = reactiveStreamsConsumer; - } - - private void eventDrivenConsumer(MessageChannel channel) { - Assert.isNull(this.pollerMetadata, - () -> "A poller should not be specified for endpoint '" + this.beanName - + "', since '" + channel + "' is a SubscribableChannel (not pollable)."); - this.endpoint = new EventDrivenConsumer((SubscribableChannel) channel, this.handler); - if (Boolean.FALSE.equals(this.autoStartup) && channel instanceof FixedSubscriberChannel) { - LOGGER.info("'autoStartup=\"false\"' has no effect when using a FixedSubscriberChannel"); - } - } - - private void pollingConsumer(MessageChannel channel) { - PollingConsumer pollingConsumer = new PollingConsumer((PollableChannel) channel, this.handler); - if (this.pollerMetadata == null) { - this.pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory); - Assert.notNull(this.pollerMetadata, - () -> "No poller has been defined for endpoint '" + this.beanName - + "', and no default poller is available within the context."); - } - pollingConsumer.setTaskExecutor(this.pollerMetadata.getTaskExecutor()); - pollingConsumer.setTrigger(this.pollerMetadata.getTrigger()); - pollingConsumer.setAdviceChain(this.pollerMetadata.getAdviceChain()); - pollingConsumer.setMaxMessagesPerPoll(this.pollerMetadata.getMaxMessagesPerPoll()); - - pollingConsumer.setErrorHandler(this.pollerMetadata.getErrorHandler()); - - pollingConsumer.setReceiveTimeout(this.pollerMetadata.getReceiveTimeout()); - pollingConsumer.setTransactionSynchronizationFactory( - this.pollerMetadata.getTransactionSynchronizationFactory()); - pollingConsumer.setBeanClassLoader(this.beanClassLoader); - pollingConsumer.setBeanFactory(this.beanFactory); - this.endpoint = pollingConsumer; - } - - private void smartLifecycle() { - if (this.autoStartup != null) { - this.endpoint.setAutoStartup(this.autoStartup); - } - if (this.isPhaseSet) { - this.endpoint.setPhase(this.phase); - } - } - - - /* - * SmartLifecycle implementation (delegates to the created endpoint) - */ - - @Override - public boolean isAutoStartup() { - return (this.endpoint == null) || this.endpoint.isAutoStartup(); - } - - @Override - public int getPhase() { - return (this.endpoint != null) ? this.endpoint.getPhase() : 0; - } - - @Override - public boolean isRunning() { - return (this.endpoint != null) && this.endpoint.isRunning(); - } - - @Override - public void start() { - if (this.endpoint != null) { - this.endpoint.start(); - } - } - - @Override - public void stop() { - if (this.endpoint != null) { - this.endpoint.stop(); - } - } - - @Override - public void stop(Runnable callback) { - if (this.endpoint != null) { - this.endpoint.stop(callback); - } - else { - callback.run(); - } - } - - @Override - public void destroy() { - if (this.endpoint != null) { - this.endpoint.destroy(); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ControlBusFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ControlBusFactoryBean.java deleted file mode 100644 index dfc12bd4826..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ControlBusFactoryBean.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2024-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.handler.ControlBusMessageProcessor; -import org.springframework.integration.handler.ServiceActivatingHandler; -import org.springframework.messaging.MessageHandler; - -/** - * FactoryBean for creating {@link MessageHandler} instances to handle a message with a Control Bus command. - * - * @author Artem Bilan - * - * @since 6.4 - */ -public class ControlBusFactoryBean extends AbstractSimpleMessageHandlerFactoryBean { - - @Nullable - private Long sendTimeout; - - public void setSendTimeout(Long sendTimeout) { - this.sendTimeout = sendTimeout; - } - - @Override - protected MessageHandler createHandler() { - ServiceActivatingHandler handler = new ServiceActivatingHandler(new ControlBusMessageProcessor()); - if (this.sendTimeout != null) { - handler.setSendTimeout(this.sendTimeout); - } - return handler; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java deleted file mode 100644 index 59b8f1fa368..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ConverterRegistrar.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.ConversionServiceFactory; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.integration.json.JsonNodeWrapperConverter; -import org.springframework.integration.support.json.JacksonPresent; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.util.Assert; - -/** - * Utility class that keeps track of a set of Converters in order to register - * them with the "integrationConversionService" upon initialization. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Jooyoung Pyoung - * - * @since 2.0 - */ -class ConverterRegistrar implements InitializingBean, ApplicationContextAware { - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - ConverterRegistrar() { - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void afterPropertiesSet() { - ConversionService conversionService = IntegrationUtils.getConversionService(this.applicationContext); - if (conversionService instanceof GenericConversionService genericConversionService) { - registerConverters(genericConversionService); - } - else { - Assert.notNull(conversionService, - () -> "Failed to locate '" + IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME + "'"); - } - } - - @SuppressWarnings("removal") - private void registerConverters(GenericConversionService conversionService) { - Set converters = - this.applicationContext.getBeansOfType(IntegrationConverterRegistration.class) - .values() - .stream().map(IntegrationConverterRegistration::converter) - .collect(Collectors.toSet()); - if (JacksonPresent.isJackson3Present()) { - converters.add(new JsonNodeWrapperConverter()); - } - - if (JacksonPresent.isJackson2Present()) { - converters.add(new org.springframework.integration.json.JsonNodeWrapperToJsonNodeConverter()); - } - ConversionServiceFactory.registerConverters(converters, conversionService); - } - - /** - * A configuration supporting bean for converter with a {@link IntegrationConverter} - * annotation. - * - * @param converter the target converter bean with a {@link IntegrationConverter}. - * - * @since 6.0 - */ - record IntegrationConverterRegistration(Object converter) { - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/CorrelationStrategyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/CorrelationStrategyFactoryBean.java deleted file mode 100644 index 995157e3495..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/CorrelationStrategyFactoryBean.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Method; - -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.aggregator.CorrelationStrategy; -import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy; -import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.util.StringUtils; - -/** - * Convenience factory for XML configuration of a {@link CorrelationStrategy}. - * Encapsulates the knowledge of the default strategy and search algorithms for POJO and annotated methods. - * - * @author Dave Syer - * @author Artem Bilan - * - */ -public class CorrelationStrategyFactoryBean implements FactoryBean, InitializingBean { - - @SuppressWarnings("NullAway.Init") - private Object target; - - @Nullable - private String methodName; - - private CorrelationStrategy strategy = - new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID); - - public CorrelationStrategyFactoryBean() { - } - - public void setTarget(Object target) { - this.target = target; - } - - public void setMethodName(String methodName) { - this.methodName = methodName; - } - - @Override - public void afterPropertiesSet() { - if (this.target instanceof CorrelationStrategy && !StringUtils.hasText(this.methodName)) { - this.strategy = (CorrelationStrategy) this.target; - return; - } - if (this.target != null) { - if (StringUtils.hasText(this.methodName)) { - this.strategy = new MethodInvokingCorrelationStrategy(this.target, this.methodName); - } - else { - Method method = MessagingAnnotationUtils.findAnnotatedMethod(this.target, - org.springframework.integration.annotation.CorrelationStrategy.class); - if (method != null) { - this.strategy = new MethodInvokingCorrelationStrategy(this.target, method); - } - } - } - } - - @NonNull - @Override - public CorrelationStrategy getObject() { - return this.strategy; - } - - @Override - public Class getObjectType() { - return CorrelationStrategy.class; - } - - @Override - public boolean isSingleton() { - return true; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/CustomConversionServiceFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/CustomConversionServiceFactoryBean.java deleted file mode 100644 index ff21e394480..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/CustomConversionServiceFactoryBean.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.jspecify.annotations.Nullable; - -import org.springframework.context.support.ConversionServiceFactoryBean; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.GenericConversionService; - -/** - * This is a workaround until SPR-8818 will be resolved. - * See INT-2259 and INT-1893 for more detail. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @since 2.0 - */ -class CustomConversionServiceFactoryBean extends ConversionServiceFactoryBean { - - @Override - public @Nullable ConversionService getObject() { - ConversionService service = super.getObject(); - if (service instanceof GenericConversionService) { - ((GenericConversionService) service).removeConvertible(Object.class, Object.class); - } - return service; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java deleted file mode 100644 index a179e40cade..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.beans.Introspector; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.HierarchicalBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.PropertiesFactoryBean; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.SmartLifecycle; -import org.springframework.core.Ordered; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.channel.ChannelUtils; -import org.springframework.integration.channel.DefaultHeaderChannelRegistry; -import org.springframework.integration.channel.MessagePublishingErrorHandler; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.channel.PublishSubscribeChannel; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.context.IntegrationProperties; -import org.springframework.integration.handler.LoggingHandler; -import org.springframework.integration.handler.support.IntegrationMessageHandlerMethodFactory; -import org.springframework.integration.json.JsonPathUtils; -import org.springframework.integration.support.DefaultMessageBuilderFactory; -import org.springframework.integration.support.SmartLifecycleRoleController; -import org.springframework.integration.support.channel.BeanFactoryChannelResolver; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter; -import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter; -import org.springframework.integration.support.management.ControlBusCommandRegistry; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.util.ClassUtils; - -/** - * A {@link BeanDefinitionRegistryPostProcessor} implementation that registers bean definitions - * for many infrastructure components with their default configurations. - * All of them can be overridden using particular bean names. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Gary Russell - * @author Michael Wiles - * @author Pierre Lakreb - * @author Chris Bono - * - * @see IntegrationContextUtils - */ -public class DefaultConfiguringBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { - - private static final LogAccessor LOGGER = new LogAccessor(DefaultConfiguringBeanFactoryPostProcessor.class); - - private static final Set REGISTRIES_PROCESSED = new HashSet<>(); - - private static final @Nullable Class XPATH_CLASS; - - private static final boolean JSON_PATH_PRESENT = ClassUtils.isPresent("com.jayway.jsonpath.JsonPath", null); - - static { - Class xpathClass = null; - try { - xpathClass = ClassUtils.forName(IntegrationContextUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils", - ClassUtils.getDefaultClassLoader()); - } - catch (@SuppressWarnings("unused") ClassNotFoundException e) { - LOGGER.debug("SpEL function '#xpath' isn't registered: " + - "there is no spring-integration-xml.jar on the classpath."); - } - finally { - XPATH_CLASS = xpathClass; - } - } - - @SuppressWarnings("NullAway.Init") - private ConfigurableListableBeanFactory beanFactory; - - @SuppressWarnings("NullAway.Init") - private BeanDefinitionRegistry registry; - - DefaultConfiguringBeanFactoryPostProcessor() { - } - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - this.registry = registry; - this.beanFactory = (ConfigurableListableBeanFactory) registry; - - registerBeanFactoryChannelResolver(); - registerMessagePublishingErrorHandler(); - registerNullChannel(); - registerErrorChannel(); - registerIntegrationEvaluationContext(); - registerTaskScheduler(); - registerIdGeneratorConfigurer(); - registerIntegrationProperties(); - registerBuiltInBeans(); - registerRoleController(); - registerMessageBuilderFactory(); - registerHeaderChannelRegistry(); - registerGlobalChannelInterceptorProcessor(); - registerDefaultDatatypeChannelMessageConverter(); - registerArgumentResolverMessageConverter(); - registerMessageHandlerMethodFactory(); - registerListMessageHandlerMethodFactory(); - registerIntegrationConfigurationReport(); - registerControlBusCommandRegistry(); - registerApplicationRunningController(); - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - } - - private void registerBeanFactoryChannelResolver() { - if (!this.beanFactory.containsBeanDefinition(ChannelResolverUtils.CHANNEL_RESOLVER_BEAN_NAME)) { - this.registry.registerBeanDefinition(ChannelResolverUtils.CHANNEL_RESOLVER_BEAN_NAME, - new RootBeanDefinition(BeanFactoryChannelResolver.class)); - } - } - - private void registerMessagePublishingErrorHandler() { - if (!this.beanFactory.containsBeanDefinition(ChannelUtils.MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME)) { - this.registry.registerBeanDefinition(ChannelUtils.MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME, - new RootBeanDefinition(MessagePublishingErrorHandler.class)); - } - } - - /** - * Register a null channel in the application context. - * The bean name is defined by the constant {@link IntegrationContextUtils#NULL_CHANNEL_BEAN_NAME}. - */ - private void registerNullChannel() { - if (this.beanFactory.containsBean(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME)) { - BeanDefinition nullChannelDefinition = null; - BeanFactory beanFactoryToUse = this.beanFactory; - do { - if (beanFactoryToUse instanceof ConfigurableListableBeanFactory listable && - listable.containsBeanDefinition(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME)) { - - nullChannelDefinition = listable.getBeanDefinition(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME); - } - if (beanFactoryToUse instanceof HierarchicalBeanFactory hierarchicalBeanFactory) { - beanFactoryToUse = hierarchicalBeanFactory.getParentBeanFactory(); - } - } - while (nullChannelDefinition == null); - - if (!NullChannel.class.getName().equals(nullChannelDefinition.getBeanClassName())) { - throw new IllegalStateException("The bean name '" + IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME - + "' is reserved."); - } - } - else { - this.registry.registerBeanDefinition(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME, - new RootBeanDefinition(NullChannel.class)); - } - } - - /** - * Register an error channel in the application context. - * The bean name is defined by the constant {@link IntegrationContextUtils#ERROR_CHANNEL_BEAN_NAME}. - * Also, a {@link IntegrationContextUtils#ERROR_LOGGER_BEAN_NAME} is registered as a subscriber for this - * error channel. - */ - private void registerErrorChannel() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)) { - LOGGER.info(() -> "No bean named '" + IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME + - "' has been explicitly defined. " + - "Therefore, a default PublishSubscribeChannel will be created."); - this.registry.registerBeanDefinition(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, - BeanDefinitionBuilder.rootBeanDefinition(PublishSubscribeChannel.class) - .addConstructorArgValue(IntegrationProperties.getExpressionFor( - IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS)) - .addPropertyValue("ignoreFailures", IntegrationProperties.getExpressionFor( - IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES)) - .getBeanDefinition()); - - String errorLoggerBeanName = - IntegrationContextUtils.ERROR_LOGGER_BEAN_NAME + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX; - this.registry.registerBeanDefinition(errorLoggerBeanName, - BeanDefinitionBuilder.genericBeanDefinition(LoggingHandler.class) - .addConstructorArgValue(LoggingHandler.Level.ERROR) - .addPropertyValue(IntegrationNamespaceUtils.ORDER, Ordered.LOWEST_PRECEDENCE - 100) - .getBeanDefinition()); - - BeanDefinitionBuilder loggingEndpointBuilder = - BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class) - .addPropertyValue("inputChannelName", IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME) - .addPropertyReference("handler", errorLoggerBeanName); - - BeanComponentDefinition componentDefinition = - new BeanComponentDefinition(loggingEndpointBuilder.getBeanDefinition(), - IntegrationContextUtils.ERROR_LOGGER_BEAN_NAME); - BeanDefinitionReaderUtils.registerBeanDefinition(componentDefinition, this.registry); - } - } - - /** - * Register {@link IntegrationEvaluationContextFactoryBean} - * and {@link IntegrationSimpleEvaluationContextFactoryBean} beans, if necessary. - */ - private void registerIntegrationEvaluationContext() { - if (!this.registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)) { - BeanDefinitionBuilder integrationEvaluationContextBuilder = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationEvaluationContextFactoryBean.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - this.registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, - integrationEvaluationContextBuilder.getBeanDefinition()); - } - - if (!this.registry.containsBeanDefinition( - IntegrationContextUtils.INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME)) { - - BeanDefinitionBuilder integrationEvaluationContextBuilder = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationSimpleEvaluationContextFactoryBean.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - this.registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME, - integrationEvaluationContextBuilder.getBeanDefinition()); - } - } - - /** - * Register an {@link IdGeneratorConfigurer} in the application context. - */ - private void registerIdGeneratorConfigurer() { - Class clazz = IdGeneratorConfigurer.class; - String className = clazz.getName(); - String[] definitionNames = this.registry.getBeanDefinitionNames(); - for (String definitionName : definitionNames) { - BeanDefinition definition = this.registry.getBeanDefinition(definitionName); - if (className.equals(definition.getBeanClassName())) { - LOGGER.info(() -> className + " is already registered and will be used"); - return; - } - } - RootBeanDefinition beanDefinition = new RootBeanDefinition(clazz); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, this.registry); - } - - /** - * Register a {@link ThreadPoolTaskScheduler} bean in the application context. - */ - private void registerTaskScheduler() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)) { - LOGGER.info(() -> "No bean named '" + IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME + - "' has been explicitly defined. " + - "Therefore, a default ThreadPoolTaskScheduler will be created."); - BeanDefinition scheduler = - BeanDefinitionBuilder.genericBeanDefinition(ThreadPoolTaskScheduler.class) - .addPropertyValue("poolSize", IntegrationProperties.getExpressionFor( - IntegrationProperties.TASK_SCHEDULER_POOL_SIZE)) - .addPropertyValue("threadNamePrefix", "task-scheduler-") - .addPropertyValue("rejectedExecutionHandler", new CallerRunsPolicy()) - .addPropertyValue("phase", SmartLifecycle.DEFAULT_PHASE / 2) - .addPropertyReference("errorHandler", - ChannelUtils.MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME) - .getBeanDefinition(); - this.registry.registerBeanDefinition(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, scheduler); - } - } - - /** - * Register an {@code integrationGlobalProperties} bean if necessary. - */ - private void registerIntegrationProperties() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME)) { - BeanDefinition userProperties = - BeanDefinitionBuilder.genericBeanDefinition(PropertiesFactoryBean.class) - .addPropertyValue("locations", "classpath*:META-INF/spring.integration.properties") - .getBeanDefinition(); - - BeanDefinition integrationProperties = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationProperties.class) - .setFactoryMethod("parse") - .addConstructorArgValue(userProperties) - .getBeanDefinition(); - - this.registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, - integrationProperties); - } - } - - /** - * Register {@code jsonPath} and {@code xpath} SpEL-function beans, if necessary. - */ - private void registerBuiltInBeans() { - int registryId = System.identityHashCode(this.registry); - jsonPath(registryId); - xpath(registryId); - REGISTRIES_PROCESSED.add(registryId); - } - - private void jsonPath(int registryId) throws LinkageError { - String jsonPathBeanName = "jsonPath"; - if (JSON_PATH_PRESENT) { - if (!this.beanFactory.containsBean(jsonPathBeanName) && !REGISTRIES_PROCESSED.contains(registryId)) { - IntegrationConfigUtils.registerSpelFunctionBean(this.registry, jsonPathBeanName, - JsonPathUtils.class, "evaluate"); - } - } - else { - LOGGER.debug("The '#jsonPath' SpEL function cannot be registered: " + - "there is no jayway json-path.jar on the classpath."); - } - } - - private void xpath(int registryId) throws LinkageError { - String xpathBeanName = "xpath"; - if (XPATH_CLASS != null - && !this.beanFactory.containsBean(xpathBeanName) - && !REGISTRIES_PROCESSED.contains(registryId)) { - - IntegrationConfigUtils.registerSpelFunctionBean(this.registry, xpathBeanName, XPATH_CLASS, "evaluate"); - } - } - - /** - * Register a {@link SmartLifecycleRoleController} if necessary. - */ - private void registerRoleController() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER)) { - this.registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER, - new RootBeanDefinition(SmartLifecycleRoleController.class)); - } - } - - /** - * Register a {@link DefaultMessageBuilderFactory} if necessary. - */ - private void registerMessageBuilderFactory() { - if (!this.beanFactory.containsBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME)) { - BeanDefinitionBuilder mbfBuilder = BeanDefinitionBuilder - .genericBeanDefinition(DefaultMessageBuilderFactory.class) - .addPropertyValue("readOnlyHeaders", - IntegrationProperties.getExpressionFor(IntegrationProperties.READ_ONLY_HEADERS)); - this.registry.registerBeanDefinition( - IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME, mbfBuilder.getBeanDefinition()); - } - } - - /** - * Register a {@link DefaultHeaderChannelRegistry} if necessary. - */ - private void registerHeaderChannelRegistry() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME)) { - LOGGER.info(() -> "No bean named '" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME + - "' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created."); - - this.registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME, - new RootBeanDefinition(DefaultHeaderChannelRegistry.class)); - } - } - - /** - * Register a {@link GlobalChannelInterceptorProcessor} if necessary. - */ - private void registerGlobalChannelInterceptorProcessor() { - if (!this.registry.containsBeanDefinition( - IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME)) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - this.registry.registerBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME, - builder.getBeanDefinition()); - } - } - - /** - * Register a {@link DefaultDatatypeChannelMessageConverter} bean if necessary. - */ - private void registerDefaultDatatypeChannelMessageConverter() { - if (!this.beanFactory.containsBean( - IntegrationContextUtils.INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME)) { - - this.registry.registerBeanDefinition( - IntegrationContextUtils.INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME, - new RootBeanDefinition(DefaultDatatypeChannelMessageConverter.class)); - } - } - - /** - * Register the default {@link ConfigurableCompositeMessageConverter} for argument - * resolvers during handler method invocation. - */ - private void registerArgumentResolverMessageConverter() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) { - this.registry.registerBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, - new RootBeanDefinition(ConfigurableCompositeMessageConverter.class)); - } - } - - private void registerMessageHandlerMethodFactory() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.MESSAGE_HANDLER_FACTORY_BEAN_NAME)) { - BeanDefinitionBuilder messageHandlerMethodFactoryBuilder = - createMessageHandlerMethodFactoryBeanDefinition(false); - this.registry.registerBeanDefinition(IntegrationContextUtils.MESSAGE_HANDLER_FACTORY_BEAN_NAME, - messageHandlerMethodFactoryBuilder.getBeanDefinition()); - } - } - - private void registerListMessageHandlerMethodFactory() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.LIST_MESSAGE_HANDLER_FACTORY_BEAN_NAME)) { - BeanDefinitionBuilder messageHandlerMethodFactoryBuilder = - createMessageHandlerMethodFactoryBeanDefinition(true); - this.registry.registerBeanDefinition(IntegrationContextUtils.LIST_MESSAGE_HANDLER_FACTORY_BEAN_NAME, - messageHandlerMethodFactoryBuilder.getBeanDefinition()); - } - } - - private void registerIntegrationConfigurationReport() { - this.registry.registerBeanDefinition(Introspector.decapitalize(IntegrationConfigurationReport.class.getName()), - BeanDefinitionBuilder.genericBeanDefinition(IntegrationConfigurationReport.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) - .getBeanDefinition()); - } - - private void registerControlBusCommandRegistry() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.CONTROL_BUS_COMMAND_REGISTRY_BEAN_NAME)) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(ControlBusCommandRegistry.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - this.registry.registerBeanDefinition(IntegrationContextUtils.CONTROL_BUS_COMMAND_REGISTRY_BEAN_NAME, - builder.getBeanDefinition()); - } - } - - private void registerApplicationRunningController() { - if (!this.beanFactory.containsBean(IntegrationContextUtils.APPLICATION_RUNNING_CONTROLLER_BEAN_NAME)) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(ApplicationRunningController.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - this.registry.registerBeanDefinition(IntegrationContextUtils.APPLICATION_RUNNING_CONTROLLER_BEAN_NAME, - builder.getBeanDefinition()); - } - } - - private static BeanDefinitionBuilder createMessageHandlerMethodFactoryBeanDefinition(boolean listCapable) { - return BeanDefinitionBuilder.genericBeanDefinition(IntegrationMessageHandlerMethodFactory.class) - .addConstructorArgValue(listCapable) - .addPropertyReference("messageConverter", - IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegration.java b/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegration.java deleted file mode 100644 index 4597732fdd9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegration.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -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.Import; - -/** - * The main configuration annotation to enable Spring Integration infrastructure: - * - Registers some built-in beans; - * - Adds several {@code BeanFactoryPostProcessor}s; - * - Adds several {@code BeanPostProcessor}s; - * - Adds annotations processors. - *

- * Add this annotation to an {@code @Configuration} class to have - * the imported Spring Integration configuration : - *

- * @Configuration
- * @EnableIntegration
- * @ComponentScan(basePackageClasses = { MyConfiguration.class })
- * public class MyIntegrationConfiguration {
- * }
- * 
- * - * @author Artem Bilan - * @since 4.0 - * @see IntegrationRegistrar - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Import(IntegrationRegistrar.class) -public @interface EnableIntegration { - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java b/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java deleted file mode 100644 index af4253ea168..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -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.Import; -import org.springframework.integration.support.management.micrometer.MicrometerMetricsCaptorImportSelector; - -/** - * Enables default configuring of management in Spring Integration components in an existing application. - * - *

The resulting {@link IntegrationManagementConfigurer} - * bean is defined under the name {@code integrationManagementConfigurer}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.2 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Import({MicrometerMetricsCaptorImportSelector.class, IntegrationManagementConfiguration.class}) -public @interface EnableIntegrationManagement { - - /** - * Use for disabling all logging in the main message flow in framework components. When 'false', - * such logging will be skipped, regardless of logging level. When 'true', - * the logging is controlled as normal by the logging subsystem log level configuration. - *

- * It has been found that in high-volume messaging environments, calls to methods such as - * {@code logger.isDebuggingEnabled()} can be quite expensive and account for an inordinate amount of CPU - * time. - *

- * Set this to false for disabling logging by default in all framework components that implement - * {@link org.springframework.integration.support.management.IntegrationManagement} - * (channels, message handlers etc.). It turns off logging such as "PreSend on channel", "Received message" etc. - *

- * After the context is initialized, individual components can have their setting changed by invoking - * {@link org.springframework.integration.support.management.IntegrationManagement#setLoggingEnabled(boolean)}. - * @return the value; true by default. - */ - String defaultLoggingEnabled() default "true"; - - /** - * Set simple pattern component names matching for observation registry injection. - * @return simple pattern component names matching for observation registry injection. - * None by default - no unconditional observation instrumentation. - * Can be set to {@code *} to instrumentation all the integration components. - * The pattern can start with {@code !} to negate the matching. - * The value can be a property placeholder and/or comma-separated. - * @since 6.0 - * @see org.springframework.integration.support.utils.PatternMatchUtils#smartMatch(String, String...) - */ - String[] observationPatterns() default {}; - - /** - * Set to {@code true} to turn on Control Bus commands loading after application context initialization. - * @return the flag to initialize the control bus registry eagerly or not. - * @since 6.4 - * @see org.springframework.integration.support.management.ControlBusCommandRegistry#setEagerInitialization(boolean) - */ - String loadControlBusCommands() default "false"; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableMessageHistory.java b/spring-integration-core/src/main/java/org/springframework/integration/config/EnableMessageHistory.java deleted file mode 100644 index eafc34fd772..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableMessageHistory.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -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.Import; - -/** - * Enables {@link org.springframework.integration.history.MessageHistory} - * for Spring Integration components. - * - * @author Artem Bilan - * - * @since 4.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Import(MessageHistoryRegistrar.class) -public @interface EnableMessageHistory { - - /** - * The list of component name patterns to track (e.g. {@code "inputChannel", "out*", "*Channel", "*Service"}). - * By default all Spring Integration components are tracked. - * @return the list of component name patterns to track - */ - String[] value() default "*"; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/EnablePublisher.java b/spring-integration-core/src/main/java/org/springframework/integration/config/EnablePublisher.java deleted file mode 100644 index c86622901de..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/EnablePublisher.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -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.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AliasFor; - -/** - * Provides the registration for the - * {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor} - * to allow the use of the {@link org.springframework.integration.annotation.Publisher} annotation. - * In addition the {@code default-publisher-channel} name can be configured as - * the {@link #defaultChannel()} of this annotation. - * - * @author Artem Bilan - * - * @since 4.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Import(PublisherRegistrar.class) -public @interface EnablePublisher { - - /** - * Alias for the {@link #defaultChannel()} attribute. - * The {@code default-publisher-channel} name. - * @return the channel bean name. - */ - @AliasFor("defaultChannel") - String value() default ""; - - /** - * The {@code default-publisher-channel} name. - * @return the channel bean name. - * @since 5.1.3 - */ - @AliasFor("value") - String defaultChannel() default ""; - - /** - * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed - * to standard Java interface-based proxies. - * @return whether proxy target class or not. - * @since 5.1.3 - */ - boolean proxyTargetClass() default false; - - /** - * Indicate the order in which the - * {@link org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor} - * should be applied. - *

The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run - * after all other post-processors, so that it can add an advisor to - * existing proxies rather than double-proxy. - * @return the order for the bean post-processor. - * @since 5.1.3 - */ - int order() default Ordered.LOWEST_PRECEDENCE; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionFactoryBean.java deleted file mode 100644 index 5b237f2eae4..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionFactoryBean.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.springframework.beans.factory.config.AbstractFactoryBean; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.SpelParserConfiguration; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.Assert; - -/** - * FactoryBean for creating Expression instances. - * - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -public class ExpressionFactoryBean extends AbstractFactoryBean { - - private static final ExpressionParser DEFAULT_PARSER = new SpelExpressionParser(); - - private final String expressionString; - - private ExpressionParser parser = DEFAULT_PARSER; - - public ExpressionFactoryBean(String expressionString) { - Assert.hasText(expressionString, "'expressionString' must not be empty or null"); - this.expressionString = expressionString; - } - - public void setParserConfiguration(SpelParserConfiguration parserConfiguration) { - Assert.notNull(parserConfiguration, "'parserConfiguration' must not be null"); - this.parser = new SpelExpressionParser(parserConfiguration); - } - - @Override - public Class getObjectType() { - return Expression.class; - } - - @Override - protected Expression createInstance() { - return this.parser.parseExpression(this.expressionString); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/FilterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/FilterAnnotationPostProcessor.java deleted file mode 100644 index c90bd009175..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/FilterAnnotationPostProcessor.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.ResolvableType; -import org.springframework.integration.annotation.Filter; -import org.springframework.integration.core.MessageSelector; -import org.springframework.integration.filter.MessageFilter; -import org.springframework.integration.filter.MethodInvokingSelector; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Post-processor for Methods annotated with {@link Filter @Filter}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @since 2.0 - */ -public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - public FilterAnnotationPostProcessor() { - this.messageHandlerAttributes.addAll(Arrays.asList("discardChannel", "throwExceptionOnRejection", - "adviceChain", "discardWithinAdvice")); - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotations) { - - BeanDefinition handlerBeanDefinition = - super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); - - if (handlerBeanDefinition != null) { - return handlerBeanDefinition; - } - - BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); - if (targetObjectBeanDefinition == null) { - targetObjectBeanDefinition = new RuntimeBeanReference(beanName); - } - - BeanDefinition filterBeanDefinition = - BeanDefinitionBuilder.genericBeanDefinition(FilterFactoryBean.class) - .addPropertyValue("targetObject", targetObjectBeanDefinition) - .getBeanDefinition(); - - new BeanDefinitionPropertiesMapper(filterBeanDefinition, annotations) - .setPropertyValue("discardWithinAdvice") - .setPropertyValue("throwExceptionOnRejection") - .setPropertyReference("discardChannel"); - - return filterBeanDefinition; - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()), - "The Filter annotation may only be applied to methods with a boolean return type."); - MessageSelector selector = new MethodInvokingSelector(bean, method); - - MessageFilter filter = new MessageFilter(selector); - - String discardWithinAdvice = - MessagingAnnotationUtils.resolveAttribute(annotations, "discardWithinAdvice", String.class); - if (StringUtils.hasText(discardWithinAdvice)) { - filter.setDiscardWithinAdvice(resolveAttributeToBoolean(discardWithinAdvice)); - } - - String throwExceptionOnRejection = - MessagingAnnotationUtils.resolveAttribute(annotations, "throwExceptionOnRejection", String.class); - if (StringUtils.hasText(throwExceptionOnRejection)) { - filter.setThrowExceptionOnRejection(resolveAttributeToBoolean(throwExceptionOnRejection)); - } - - String discardChannelName = - MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class); - if (StringUtils.hasText(discardChannelName)) { - filter.setDiscardChannelName(discardChannelName); - } - - setOutputChannelIfPresent(annotations, filter); - return filter; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/FilterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/FilterFactoryBean.java deleted file mode 100644 index e0f4ea17ef7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/FilterFactoryBean.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.core.MessageSelector; -import org.springframework.integration.filter.ExpressionEvaluatingSelector; -import org.springframework.integration.filter.MessageFilter; -import org.springframework.integration.filter.MethodInvokingSelector; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Factory bean for creating a Message Filter. - * - * @author Mark Fisher - * @author Gary Russell - * @author David Liu - * @author Artem Bilan - * - * @since 2.0 - */ -public class FilterFactoryBean extends AbstractStandardMessageHandlerFactoryBean { - - @Nullable - private volatile MessageChannel discardChannel; - - @Nullable - private volatile Boolean throwExceptionOnRejection; - - @Nullable - private volatile Boolean discardWithinAdvice; - - public void setDiscardChannel(MessageChannel discardChannel) { - this.discardChannel = discardChannel; - } - - public void setThrowExceptionOnRejection(Boolean throwExceptionOnRejection) { - this.throwExceptionOnRejection = throwExceptionOnRejection; - } - - public void setDiscardWithinAdvice(boolean discardWithinAdvice) { - this.discardWithinAdvice = discardWithinAdvice; - } - - @Override - protected MessageHandler createMethodInvokingHandler(Object targetObject, @Nullable String targetMethodName) { - MessageSelector selector = null; - if (targetObject instanceof MessageSelector) { - selector = (MessageSelector) targetObject; - } - else if (StringUtils.hasText(targetMethodName)) { - this.checkForIllegalTarget(targetObject, targetMethodName); - selector = new MethodInvokingSelector(targetObject, targetMethodName); - } - else { - selector = new MethodInvokingSelector(targetObject); - } - return this.createFilter(selector); - } - - @Override - protected void checkForIllegalTarget(Object targetObject, @Nullable String targetMethodName) { - if (targetObject instanceof AbstractReplyProducingMessageHandler - && this.methodIsHandleMessageOrEmpty(targetMethodName)) { - throw new IllegalArgumentException("You cannot use 'AbstractReplyProducingMessageHandler.handleMessage()' " - + "as a filter - it does not return a result"); - } - } - - @Override - protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - return this.createFilter(new ExpressionEvaluatingSelector(expression)); - } - - protected MessageFilter createFilter(MessageSelector selector) { - MessageFilter filter = new MessageFilter(selector); - postProcessReplyProducer(filter); - return filter; - } - - protected void postProcessFilter(MessageFilter filter) { - if (this.throwExceptionOnRejection != null) { - filter.setThrowExceptionOnRejection(this.throwExceptionOnRejection); - } - if (this.discardChannel != null) { - filter.setDiscardChannel(this.discardChannel); - } - if (this.discardWithinAdvice != null) { - filter.setDiscardWithinAdvice(this.discardWithinAdvice); - } - } - - @Override - protected void postProcessReplyProducer(AbstractMessageProducingHandler handler) { - super.postProcessReplyProducer(handler); - - if (!(handler instanceof MessageFilter)) { - Assert.isNull(this.throwExceptionOnRejection, - "Cannot set throwExceptionOnRejection if the referenced bean is " - + "an AbstractReplyProducingMessageHandler, but not a MessageFilter"); - Assert.isNull(this.discardChannel, "Cannot set discardChannel if the referenced bean is " - + "an AbstractReplyProducingMessageHandler, but not a MessageFilter"); - Assert.isNull(this.discardWithinAdvice, "Cannot set discardWithinAdvice if the referenced bean is " - + "an AbstractReplyProducingMessageHandler, but not a MessageFilter"); - } - else { - postProcessFilter((MessageFilter) handler); - } - } - - /** - * MessageFilter is an ARPMH. If a non-MessageFilter ARPMH is also a - * MessageSelector, MesageSelector wins and gets wrapped in a MessageFilter. - */ - @Override - protected boolean canBeUsedDirect(AbstractMessageProducingHandler handler) { - return handler instanceof MessageFilter - || (!(handler instanceof MessageSelector) && noFilterAttributesProvided()); - } - - private boolean noFilterAttributesProvided() { - return this.discardChannel == null - && this.throwExceptionOnRejection == null - && this.discardWithinAdvice == null; - } - - @Override - protected Class getPreCreationHandlerType() { - return MessageFilter.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/FixedSubscriberChannelBeanFactoryPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/FixedSubscriberChannelBeanFactoryPostProcessor.java deleted file mode 100644 index 0fede71f349..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/FixedSubscriberChannelBeanFactoryPostProcessor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Map; -import java.util.Map.Entry; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.integration.channel.FixedSubscriberChannel; -import org.springframework.util.Assert; - -/** - * Used to post process candidates for {@link FixedSubscriberChannel} - * {@link org.springframework.messaging.MessageHandler}s. - * @author Gary Russell - * @author Ngoc Nhan - * @since 4.0 - * - */ -public final class FixedSubscriberChannelBeanFactoryPostProcessor implements BeanFactoryPostProcessor { - - private final Map candidateFixedChannelHandlerMap; - - FixedSubscriberChannelBeanFactoryPostProcessor(Map candidateHandlers) { - this.candidateFixedChannelHandlerMap = candidateHandlers; - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - if (beanFactory instanceof BeanDefinitionRegistry) { - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - if (!this.candidateFixedChannelHandlerMap.isEmpty()) { - for (Entry entry : this.candidateFixedChannelHandlerMap.entrySet()) { - String handlerName = entry.getKey(); - String channelName = entry.getValue(); - BeanDefinition handlerBeanDefinition = null; - if (registry.containsBeanDefinition(handlerName)) { - handlerBeanDefinition = registry.getBeanDefinition(handlerName); - } - if (handlerBeanDefinition != null && registry.containsBeanDefinition(channelName)) { - BeanDefinition inputChannelDefinition = registry.getBeanDefinition(channelName); - if (FixedSubscriberChannel.class.getName().equals(inputChannelDefinition.getBeanClassName())) { - ConstructorArgumentValues constructorArgumentValues = inputChannelDefinition - .getConstructorArgumentValues(); - Assert.isTrue(constructorArgumentValues.isEmpty(), - "Only one subscriber is allowed for a FixedSubscriberChannel."); - constructorArgumentValues.addGenericArgumentValue(handlerBeanDefinition); - } - } - } - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java deleted file mode 100644 index c5991b73f24..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; -import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; -import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.annotation.ScannedGenericBeanDefinition; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.integration.annotation.MessagingGateway; -import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean; - -/** - * The {@link InstantiationAwareBeanPostProcessor} to wrap beans for {@link MessagingGateway} - * into {@link AnnotationGatewayProxyFactoryBean}. - * - * @author Artem Bilan - * - * @since 6.0 - * - * @see AnnotationGatewayProxyFactoryBean - */ -class GatewayProxyInstantiationPostProcessor implements - InstantiationAwareBeanPostProcessor, BeanRegistrationAotProcessor, ApplicationContextAware, AopInfrastructureBean { - - private final BeanDefinitionRegistry registry; - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - GatewayProxyInstantiationPostProcessor(BeanDefinitionRegistry registry) { - this.registry = registry; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public @Nullable Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { - if (beanClass.isInterface() && AnnotatedElementUtils.hasAnnotation(beanClass, MessagingGateway.class)) { - BeanDefinition beanDefinition = this.registry.getBeanDefinition(beanName); - if (beanDefinition instanceof AnnotatedGenericBeanDefinition - || beanDefinition instanceof ScannedGenericBeanDefinition) { - - AnnotationGatewayProxyFactoryBean gatewayProxyFactoryBean = - new AnnotationGatewayProxyFactoryBean<>(beanClass); - gatewayProxyFactoryBean.setApplicationContext(this.applicationContext); - gatewayProxyFactoryBean.setBeanFactory(this.applicationContext.getAutowireCapableBeanFactory()); - ClassLoader classLoader = this.applicationContext.getClassLoader(); - if (classLoader != null) { - gatewayProxyFactoryBean.setBeanClassLoader(classLoader); - } - gatewayProxyFactoryBean.setBeanName(beanName); - gatewayProxyFactoryBean.afterPropertiesSet(); - return gatewayProxyFactoryBean; - } - } - return null; - } - - @Override - public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { - Class beanClass = registeredBean.getBeanClass(); - if (beanClass.isInterface() && AnnotatedElementUtils.hasAnnotation(beanClass, MessagingGateway.class)) { - RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); - beanDefinition.setBeanClass(AnnotationGatewayProxyFactoryBean.class); - beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, beanClass); - beanDefinition.setTargetType( - ResolvableType.forClassWithGenerics(AnnotationGatewayProxyFactoryBean.class, beanClass)); - } - return null; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java deleted file mode 100644 index 3e6bec867ad..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -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; - -/** - * {@link org.springframework.messaging.support.ChannelInterceptor} components with this - * annotation will be applied as global channel interceptors - * using the provided {@code patterns} to match channel names. - *

- * This annotation can be used at the {@code class} level - * for {@link org.springframework.stereotype.Component} beans - * and on methods with {@link org.springframework.context.annotation.Bean}. - *

- * This annotation is an analogue of {@code }. - * - * @author Artem Bilan - * @author Meherzad Lahewala - * - * @since 4.0 - */ -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface GlobalChannelInterceptor { - - /** - * An array of patterns against which channel names will be matched. - * Since version 5.0 negative patterns are also supported. - * A leading '!' negates the pattern match. - * Default is "*" (all channels). - * @return The pattern. - * @see org.springframework.integration.support.utils.PatternMatchUtils#smartMatch(String, String...) - */ - String[] patterns() default "*"; - - /** - * The order of the interceptor. Interceptors with negative order values will be placed before any - * explicit interceptors on the channel; interceptors with positive order values will be - * placed after explicit interceptors. - * @return The order. - */ - int order() default 0; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java deleted file mode 100644 index 5df4cec4258..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.MethodMetadata; -import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; -import org.springframework.util.CollectionUtils; - -/** - * The {@link IntegrationConfigurationInitializer} to populate {@link GlobalChannelInterceptorWrapper} - * for {@link org.springframework.messaging.support.ChannelInterceptor}s marked with - * {@link GlobalChannelInterceptor} annotation. - *

- * {@link org.springframework.context.annotation.Bean} methods are also processed. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.0 - */ -public class GlobalChannelInterceptorInitializer implements IntegrationConfigurationInitializer { - - @Override - public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - - for (String beanName : registry.getBeanDefinitionNames()) { - BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); - Map interceptorAttributes = obtainGlobalChannelInterceptorAttributes(beanDefinition); - if (!CollectionUtils.isEmpty(interceptorAttributes)) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class) - .addConstructorArgReference(beanName) - .addPropertyValue("patterns", interceptorAttributes.get("patterns")) - .addPropertyValue("order", interceptorAttributes.get("order")); - - BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); - } - } - } - - @Nullable - private static Map obtainGlobalChannelInterceptorAttributes(BeanDefinition beanDefinition) { - Map annotationAttributes = null; - if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { - AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata(); - annotationAttributes = metadata.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); - if (CollectionUtils.isEmpty(annotationAttributes) - && beanDefinition.getSource() instanceof MethodMetadata beanMethod) { - - annotationAttributes = beanMethod.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); - } - } - return annotationAttributes; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java deleted file mode 100644 index 21ef6f3e293..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.core.OrderComparator; -import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; -import org.springframework.integration.channel.interceptor.VetoCapableInterceptor; -import org.springframework.integration.support.utils.PatternMatchUtils; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.InterceptableChannel; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * This class applies global interceptors ({@code } or {@code @GlobalChannelInterceptor}) - * to message channels beans. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Meherzad Lahewala - * - * @since 2.0 - */ -public final class GlobalChannelInterceptorProcessor - implements BeanFactoryAware, SmartInitializingSingleton, BeanPostProcessor, AopInfrastructureBean { - - private static final Log LOGGER = LogFactory.getLog(GlobalChannelInterceptorProcessor.class); - - private final OrderComparator comparator = new OrderComparator(); - - private final Set positiveOrderInterceptors = new LinkedHashSet<>(); - - private final Set negativeOrderInterceptors = new LinkedHashSet<>(); - - @SuppressWarnings("NullAway.Init") - private ListableBeanFactory beanFactory; - - private volatile boolean singletonsInstantiated; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - Assert.isInstanceOf(ListableBeanFactory.class, beanFactory); - this.beanFactory = (ListableBeanFactory) beanFactory; //NOSONAR (inconsistent sync) - } - - @Override - public void afterSingletonsInstantiated() { - Collection interceptors = - this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class).values(); - if (CollectionUtils.isEmpty(interceptors)) { - LOGGER.debug("No global channel interceptors."); - } - else { - interceptors.forEach(interceptor -> { - if (interceptor.getOrder() >= 0) { - this.positiveOrderInterceptors.add(interceptor); - } - else { - this.negativeOrderInterceptors.add(interceptor); - } - }); - - this.beanFactory.getBeansOfType(InterceptableChannel.class) - .forEach((key, value) -> addMatchingInterceptors(value, key)); - } - - this.singletonsInstantiated = true; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (this.singletonsInstantiated && bean instanceof InterceptableChannel) { - addMatchingInterceptors((InterceptableChannel) bean, beanName); - } - return bean; - } - - /** - * Add any interceptor whose pattern matches against the channel's name. - * @param channel the message channel to add interceptors. - * @param beanName the message channel bean name to match the pattern. - */ - public void addMatchingInterceptors(InterceptableChannel channel, String beanName) { - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Applying global interceptors on channel '" + beanName + "'"); - } - - List tempInterceptors = new ArrayList<>(); - - this.positiveOrderInterceptors - .forEach(interceptorWrapper -> - addMatchingInterceptors(beanName, tempInterceptors, interceptorWrapper)); - - tempInterceptors.sort(this.comparator); - - tempInterceptors - .stream() - .map(GlobalChannelInterceptorWrapper::getChannelInterceptor) - .filter(interceptor -> !(interceptor instanceof VetoCapableInterceptor) - || ((VetoCapableInterceptor) interceptor).shouldIntercept(beanName, channel)) - .forEach(channel::addInterceptor); - - tempInterceptors.clear(); - - this.negativeOrderInterceptors - .forEach(interceptorWrapper -> - addMatchingInterceptors(beanName, tempInterceptors, interceptorWrapper)); - - tempInterceptors.sort(this.comparator); - - if (!tempInterceptors.isEmpty()) { - for (int i = tempInterceptors.size() - 1; i >= 0; i--) { - ChannelInterceptor channelInterceptor = tempInterceptors.get(i).getChannelInterceptor(); - if (!(channelInterceptor instanceof VetoCapableInterceptor) - || ((VetoCapableInterceptor) channelInterceptor).shouldIntercept(beanName, channel)) { - channel.addInterceptor(0, channelInterceptor); - } - } - } - } - - private static void addMatchingInterceptors(String beanName, - List tempInterceptors, - GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper) { - - @Nullable String[] patterns = globalChannelInterceptorWrapper.getPatterns(); - patterns = StringUtils.trimArrayElements(patterns); - if (beanName != null && Boolean.TRUE.equals(PatternMatchUtils.smartMatch(beanName, patterns))) { - tempInterceptors.add(globalChannelInterceptorWrapper); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IdGeneratorConfigurer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IdGeneratorConfigurer.java deleted file mode 100644 index 00b45c548ba..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IdGeneratorConfigurer.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ApplicationContextEvent; -import org.springframework.context.event.ContextClosedEvent; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.IdGenerator; -import org.springframework.util.ReflectionUtils; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Christian Tzolov - * - * @since 2.0.4 - */ -public final class IdGeneratorConfigurer implements ApplicationListener { - - private final Lock lock = new ReentrantLock(); - - private static final Set GENERATOR_CONTEXT_ID = new HashSet<>(); - - private static @Nullable IdGenerator theIdGenerator; - - private final Log logger = LogFactory.getLog(getClass()); - - @Override - public void onApplicationEvent(ApplicationContextEvent event) { - this.lock.lock(); - try { - - ApplicationContext context = event.getApplicationContext(); - if (event instanceof ContextRefreshedEvent) { - boolean contextHasIdGenerator = context.getBeanNamesForType(IdGenerator.class).length > 0; - if (contextHasIdGenerator && setIdGenerator(context)) { - IdGeneratorConfigurer.GENERATOR_CONTEXT_ID.add(Objects.requireNonNull(context.getId())); - } - } - else if (event instanceof ContextClosedEvent - && IdGeneratorConfigurer.GENERATOR_CONTEXT_ID.contains(context.getId())) { - - if (IdGeneratorConfigurer.GENERATOR_CONTEXT_ID.size() == 1) { - unsetIdGenerator(); - } - IdGeneratorConfigurer.GENERATOR_CONTEXT_ID.remove(context.getId()); - } - } - finally { - this.lock.unlock(); - } - } - - private boolean setIdGenerator(ApplicationContext context) { - try { - IdGenerator idGeneratorBean = context.getBean(IdGenerator.class); - if (this.logger.isDebugEnabled()) { - this.logger.debug("using custom MessageHeaders.IdGenerator [" + idGeneratorBean.getClass() + "]"); - } - Field idGeneratorField = ReflectionUtils.findField(MessageHeaders.class, "idGenerator"); - ReflectionUtils.makeAccessible(Objects.requireNonNull(idGeneratorField)); - IdGenerator currentIdGenerator = (IdGenerator) ReflectionUtils.getField(Objects.requireNonNull(idGeneratorField), null); - if (currentIdGenerator != null) { - if (currentIdGenerator.equals(idGeneratorBean)) { - // same instance is already set, nothing needs to be done - return false; - } - else { - if ((IdGeneratorConfigurer.theIdGenerator != null ? - IdGeneratorConfigurer.theIdGenerator.getClass() : null) == idGeneratorBean.getClass()) { - if (this.logger.isWarnEnabled()) { - this.logger.warn("Another instance of " + idGeneratorBean.getClass() + - " has already been established; ignoring"); - } - return true; - } - else { - // different instance has been set, not legal - throw new BeanDefinitionStoreException("'MessageHeaders.idGenerator' has already been set and " + - "can not be set again"); - } - } - } - if (this.logger.isInfoEnabled()) { - this.logger.info("Message IDs will be generated using custom IdGenerator [" + idGeneratorBean - .getClass() + "]"); - } - ReflectionUtils.setField(idGeneratorField, null, idGeneratorBean); - IdGeneratorConfigurer.theIdGenerator = idGeneratorBean; - } - catch (@SuppressWarnings("unused") NoSuchBeanDefinitionException e) { - // No custom IdGenerator. We will use the default. - noSuchBean(context); - return false; - } - catch (IllegalStateException e) { - // thrown from ReflectionUtils - illegalState(e); - return false; - } - return true; - } - - private void noSuchBean(ApplicationContext context) { - int idBeans = context.getBeansOfType(IdGenerator.class).size(); - if (idBeans > 1 && this.logger.isWarnEnabled()) { - this.logger.warn("Found too many 'IdGenerator' beans (" + idBeans + ") " + - "Will use the existing UUID strategy."); - } - else if (this.logger.isDebugEnabled()) { - this.logger.debug("Unable to locate MessageHeaders.IdGenerator. Will use the existing UUID strategy."); - } - } - - private void illegalState(IllegalStateException e) { - if (this.logger.isWarnEnabled()) { - this.logger.warn("Unexpected exception occurred while accessing idGenerator of MessageHeaders." + - " Will use the existing UUID strategy.", e); - } - } - - private void unsetIdGenerator() { - try { - Field idGeneratorField = ReflectionUtils.findField(MessageHeaders.class, "idGenerator"); - ReflectionUtils.makeAccessible(Objects.requireNonNull(idGeneratorField)); - idGeneratorField.set(null, null); - IdGeneratorConfigurer.theIdGenerator = null; - } - catch (Exception e) { - if (this.logger.isWarnEnabled()) { - this.logger.warn("Unexpected exception occurred while accessing idGenerator of MessageHeaders.", e); - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreator.java deleted file mode 100644 index ffefd1d86fc..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreator.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.Advisor; -import org.springframework.aop.TargetSource; -import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator; -import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; -import org.springframework.aop.support.NameMatchMethodPointcut; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.PatternMatchUtils; - -/** - * The {@link AbstractAutoProxyCreator} implementation that applies {@code IdempotentReceiverInterceptor}s - * to {@link MessageHandler}s mapped by their {@code endpoint beanName}. - * - * @author Artem Bilan - * @author Gary Russell - * @author Christian Tzolov - * - * @since 4.1 - */ -@SuppressWarnings("serial") -class IdempotentReceiverAutoProxyCreator extends AbstractAutoProxyCreator { - - @SuppressWarnings("NullAway.Init") - private volatile List> idempotentEndpointsMapping; - - private volatile @Nullable Map> idempotentEndpoints; // double check locking requires volatile - - private final Lock lock = new ReentrantLock(); - - public void setIdempotentEndpointsMapping(List> idempotentEndpointsMapping) { - Assert.notEmpty(idempotentEndpointsMapping, "'idempotentEndpointsMapping' must not be empty"); - this.idempotentEndpointsMapping = idempotentEndpointsMapping; //NOSONAR (inconsistent sync) - } - - @Override - protected Object @Nullable [] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, - @Nullable TargetSource customTargetSource) throws BeansException { - initIdempotentEndpointsIfNecessary(); - - if (MessageHandler.class.isAssignableFrom(beanClass)) { - List interceptors = new ArrayList(); - for (Map.Entry> entry : Objects.requireNonNull(this.idempotentEndpoints).entrySet()) { - List mappedNames = entry.getValue(); - for (String mappedName : mappedNames) { - if (isMatch(mappedName, beanName)) { - DefaultBeanFactoryPointcutAdvisor idempotentReceiverInterceptor - = new DefaultBeanFactoryPointcutAdvisor(); - idempotentReceiverInterceptor.setAdviceBeanName(entry.getKey()); - NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); - pointcut.setMappedName("handleMessage"); - idempotentReceiverInterceptor.setPointcut(pointcut); - BeanFactory beanFactory = getBeanFactory(); - if (beanFactory != null) { - idempotentReceiverInterceptor.setBeanFactory(beanFactory); - } - interceptors.add(idempotentReceiverInterceptor); - } - } - } - if (!interceptors.isEmpty()) { - return interceptors.toArray(); - } - } - return DO_NOT_PROXY; - } - - private void initIdempotentEndpointsIfNecessary() { - if (this.idempotentEndpoints == null) { // NOSONAR (inconsistent sync) - this.lock.lock(); - try { - if (this.idempotentEndpoints == null) { - this.idempotentEndpoints = new LinkedHashMap>(); - for (Map mapping : this.idempotentEndpointsMapping) { - Assert.isTrue(mapping.size() == 1, "The 'idempotentEndpointMapping' must be a SingletonMap"); - String interceptor = mapping.keySet().iterator().next(); - String endpoint = mapping.values().iterator().next(); - Assert.hasText(interceptor, "The 'idempotentReceiverInterceptor' can't be empty String"); - Assert.hasText(endpoint, "The 'idempotentReceiverEndpoint' can't be empty String"); - List endpoints = this.idempotentEndpoints.get(interceptor); - if (endpoints == null) { - endpoints = new ArrayList(); - this.idempotentEndpoints.put(interceptor, endpoints); - } - endpoints.add(endpoint); - } - } - } - finally { - this.lock.unlock(); - } - } - } - - private boolean isMatch(String mappedName, String beanName) { - boolean matched = PatternMatchUtils.simpleMatch(mappedName, beanName); - if (!matched) { - BeanFactory beanFactory = getBeanFactory(); - if (beanFactory != null) { - String[] aliases = beanFactory.getAliases(beanName); - for (String alias : aliases) { - matched = PatternMatchUtils.simpleMatch(mappedName, alias); - if (matched) { - break; - } - } - } - } - return matched; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.java deleted file mode 100644 index af8654aa009..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.CannotLoadBeanClassException; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.core.type.MethodMetadata; -import org.springframework.core.type.StandardMethodMetadata; -import org.springframework.integration.annotation.IdempotentReceiver; -import org.springframework.integration.handler.advice.IdempotentReceiverInterceptor; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * The {@link IntegrationConfigurationInitializer} that populates - * the {@link ConfigurableListableBeanFactory} - * with an {@link IdempotentReceiverAutoProxyCreator} - * when {@code IdempotentReceiverInterceptor} {@link BeanDefinition}s and their {@code mapping} - * to Consumer Endpoints are present. - * - * @author Artem Bilan - * @author Chris Bono - * - * @since 4.1 - */ -public class IdempotentReceiverAutoProxyCreatorInitializer implements IntegrationConfigurationInitializer { - - public static final String IDEMPOTENT_ENDPOINTS_MAPPING = "IDEMPOTENT_ENDPOINTS_MAPPING"; - - private static final String IDEMPOTENT_RECEIVER_AUTO_PROXY_CREATOR_BEAN_NAME = - IdempotentReceiverAutoProxyCreator.class.getName(); - - @Override - public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - - List> idempotentEndpointsMapping = new ManagedList<>(); - - for (String beanName : registry.getBeanDefinitionNames()) { - BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); - if (IdempotentReceiverInterceptor.class.getName().equals(beanDefinition.getBeanClassName())) { - Object value = beanDefinition.removeAttribute(IDEMPOTENT_ENDPOINTS_MAPPING); - Assert.isInstanceOf(String.class, value, - "The 'mapping' of BeanDefinition 'IDEMPOTENT_ENDPOINTS_MAPPING' must be String."); - String mapping = (String) value; - String[] endpoints = StringUtils.tokenizeToStringArray(mapping, ","); - for (String endpoint : endpoints) { - Map idempotentEndpoint = new ManagedMap<>(); - idempotentEndpoint.put(beanName, - beanFactory.resolveEmbeddedValue(endpoint) + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX); - idempotentEndpointsMapping.add(idempotentEndpoint); - } - } - else if (beanDefinition instanceof AnnotatedBeanDefinition) { - annotated(beanFactory, idempotentEndpointsMapping, beanName, beanDefinition); - } - } - - if (!idempotentEndpointsMapping.isEmpty()) { - BeanDefinition bd = - BeanDefinitionBuilder.rootBeanDefinition(IdempotentReceiverAutoProxyCreator.class) - .addPropertyValue("idempotentEndpointsMapping", idempotentEndpointsMapping) - .getBeanDefinition(); - registry.registerBeanDefinition(IDEMPOTENT_RECEIVER_AUTO_PROXY_CREATOR_BEAN_NAME, bd); - } - } - - private void annotated(ConfigurableListableBeanFactory beanFactory, - List> idempotentEndpointsMapping, String beanName, BeanDefinition beanDefinition) - throws LinkageError { - - if (beanDefinition.getSource() instanceof MethodMetadata beanMethod) { - String annotationType = IdempotentReceiver.class.getName(); - if (beanMethod.isAnnotated(annotationType)) { // NOSONAR never null - Object value = Objects.requireNonNull(beanMethod.getAnnotationAttributes(annotationType)).get("value"); - if (value != null) { - - Class returnType; - if (beanMethod instanceof StandardMethodMetadata) { - returnType = ((StandardMethodMetadata) beanMethod).getIntrospectedMethod() - .getReturnType(); - } - else { - try { - returnType = ClassUtils.forName(beanMethod.getReturnTypeName(), - beanFactory.getBeanClassLoader()); - } - catch (ClassNotFoundException e) { - throw new CannotLoadBeanClassException(beanDefinition.getDescription(), - beanName, beanMethod.getReturnTypeName(), e); - } - } - - String endpoint = beanName; - if (!MessageHandler.class.isAssignableFrom(returnType)) { - endpoint = beanName + ".*" + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX; - } - - String[] interceptors = (String[]) value; - for (String interceptor : interceptors) { - Map idempotentEndpoint = new ManagedMap<>(); - idempotentEndpoint.put(interceptor, endpoint); - idempotentEndpointsMapping.add(idempotentEndpoint); - } - } - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/InboundChannelAdapterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/InboundChannelAdapterAnnotationPostProcessor.java deleted file mode 100644 index fca6ecdc054..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/InboundChannelAdapterAnnotationPostProcessor.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.Supplier; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.parsing.ComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.integration.annotation.InboundChannelAdapter; -import org.springframework.integration.annotation.Poller; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.endpoint.MethodInvokingMessageSource; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.util.ClassUtils; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; - -/** - * Post-processor for Methods annotated with {@link InboundChannelAdapter @InboundChannelAdapter}. - * - * @author Artem Bilan - * @author Gary Russell - * @author Oleg Zhurakousky - * @author Chris Bono - * - * @since 4.0 - */ -public class InboundChannelAdapterAnnotationPostProcessor extends - AbstractMethodAnnotationPostProcessor { - - @Override - public String getInputChannelAttribute() { - return "value"; - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotationChain) { - - Class handlerBeanClass = handlerBeanType.toClass(); - - if (MessageSource.class.isAssignableFrom(handlerBeanClass)) { - return beanDefinition; - } - - Method method = null; - if (Supplier.class.isAssignableFrom(handlerBeanClass)) { - method = ClassUtils.SUPPLIER_GET_METHOD; - } - else if (ClassUtils.isKotlinFunction0(handlerBeanClass)) { - method = ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD; - } - - if (method != null) { - return BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageSource.class) - .addPropertyReference("object", beanName) - .addPropertyValue("method", method) - .getBeanDefinition(); - } - else { - throw new IllegalArgumentException( - "The '" + this.annotationType + "' on @Bean method level is allowed only for: " + - MessageSource.class.getName() + ", or " + Supplier.class.getName() + - (ClassUtils.KOTLIN_FUNCTION_0_CLASS != null - ? ", or " + ClassUtils.KOTLIN_FUNCTION_0_CLASS.getName() - : "") + " beans"); - } - } - - @Override - protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition, - ComponentDefinition beanDefinition, List annotations) { - - String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class); - Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty."); - return BeanDefinitionBuilder.rootBeanDefinition(SourcePollingChannelAdapterFactoryBean.class) - .addPropertyValue("outputChannelName", channelName) - .addPropertyReference("source", handlerBeanDefinition.getName()) - .getBeanDefinition(); - } - - @Override - public Object postProcess(Object bean, String beanName, Method method, List annotations) { - String channelName = - MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class); - Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty."); - - MessageSource messageSource = createMessageSource(bean, beanName, method); - - SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter(); - adapter.setOutputChannelName(channelName); - adapter.setSource(messageSource); - Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class); - configurePollingEndpoint(adapter, poller); - - return adapter; - } - - private MessageSource createMessageSource(Object bean, String beanName, Method method) { - MethodInvokingMessageSource methodInvokingMessageSource = new MethodInvokingMessageSource(); - methodInvokingMessageSource.setObject(bean); - methodInvokingMessageSource.setMethod(method); - String messageSourceBeanName = generateHandlerBeanName(beanName, method); - getDefinitionRegistry().registerBeanDefinition(messageSourceBeanName, - new RootBeanDefinition(MethodInvokingMessageSource.class, () -> methodInvokingMessageSource)); - return getBeanFactory().getBean(messageSourceBeanName, MessageSource.class); - } - - @Override - protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations, - @Nullable String methodName) { - - return super.generateHandlerBeanName(originalBeanName, mergedAnnotations, methodName) - .replaceFirst(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX + '$', ".source"); - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - throw new UnsupportedOperationException(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java deleted file mode 100644 index 953979895e8..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.Aware; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.context.EnvironmentAware; -import org.springframework.context.ResourceLoaderAware; -import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.context.annotation.FilterType; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.env.Environment; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.core.type.filter.AspectJTypeFilter; -import org.springframework.core.type.filter.AssignableTypeFilter; -import org.springframework.core.type.filter.RegexPatternTypeFilter; -import org.springframework.core.type.filter.TypeFilter; -import org.springframework.integration.annotation.IntegrationComponentScan; -import org.springframework.integration.annotation.MessagingGateway; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * {@link ImportBeanDefinitionRegistrar} implementation to scan and register Integration specific components. - * - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * - * @since 4.0 - */ -public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar, - ResourceLoaderAware, EnvironmentAware { - - private static final Log LOGGER = LogFactory.getLog(IntegrationComponentScanRegistrar.class); - - private static final String BEAN_NAME = IntegrationComponentScanRegistrar.class.getName(); - - private final List defaultFilters = new ArrayList<>(); - - @SuppressWarnings("NullAway.Init") - private ResourceLoader resourceLoader; - - @SuppressWarnings("NullAway.Init") - private Environment environment; - - public IntegrationComponentScanRegistrar() { - this.defaultFilters.add(new AnnotationTypeFilter(MessagingGateway.class, true)); - } - - @Override - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - AnnotationAttributes componentScan = - AnnotationAttributes.fromMap( - importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName())); - - Assert.notNull(componentScan, "The '@IntegrationComponentScan' must be present for using this registrar"); - - if (registry.containsBeanDefinition(BEAN_NAME)) { - LOGGER.warn("Only one '@IntegrationComponentScan' can be present.\nConsider to merge them all to one."); - return; - } - - registry.registerBeanDefinition(BEAN_NAME, - BeanDefinitionBuilder.genericBeanDefinition(IntegrationComponentScanRegistrar.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) - .getBeanDefinition()); - - Collection basePackages = getBasePackages(componentScan, registry); - - if (basePackages.isEmpty()) { - basePackages = Collections.singleton(ClassUtils.getPackageName(importingClassMetadata.getClassName())); - } - - ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false) { - - @Override - protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { - return beanDefinition.getMetadata().isIndependent() - && !beanDefinition.getMetadata().isAnnotation(); - } - - }; - - filter(registry, componentScan, scanner); // NOSONAR - never null - - scanner.setResourceLoader(this.resourceLoader); - scanner.setEnvironment(this.environment); - - BeanNameGenerator beanNameGenerator = IntegrationConfigUtils.annotationBeanNameGenerator(registry); - - Class generatorClass = componentScan.getClass("nameGenerator"); - if (!(BeanNameGenerator.class == generatorClass)) { - beanNameGenerator = BeanUtils.instantiateClass(generatorClass); - } - - scanner.setBeanNameGenerator(beanNameGenerator); - scanner.scan(basePackages.toArray(String[]::new)); - } - - private void filter(BeanDefinitionRegistry registry, AnnotationAttributes componentScan, - ClassPathScanningCandidateComponentProvider scanner) { - - if (componentScan.getBoolean("useDefaultFilters")) { // NOSONAR - never null - for (TypeFilter typeFilter : this.defaultFilters) { - scanner.addIncludeFilter(typeFilter); - } - } - - for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) { - scanner.addIncludeFilter(typeFilter); - } - } - for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) { - scanner.addExcludeFilter(typeFilter); - } - } - } - - protected Collection getBasePackages(AnnotationAttributes componentScan, - @SuppressWarnings("unused") BeanDefinitionRegistry registry) { - - Set basePackages = new HashSet<>(); - - for (String pkg : componentScan.getStringArray("value")) { - if (StringUtils.hasText(pkg)) { - basePackages.add(pkg); - } - } - - for (Class clazz : componentScan.getClassArray("basePackageClasses")) { - basePackages.add(ClassUtils.getPackageName(clazz)); - } - - return basePackages; - } - - private List typeFiltersFor(AnnotationAttributes filter, BeanDefinitionRegistry registry) { - List typeFilters = new ArrayList<>(); - FilterType filterType = filter.getEnum("type"); - - for (Class filterClass : filter.getClassArray("classes")) { - switch (filterType) { - case ANNOTATION -> { - Assert.isAssignable(Annotation.class, filterClass, - "An error occurred while processing a @IntegrationComponentScan ANNOTATION type filter: "); - @SuppressWarnings("unchecked") - Class annotationType = (Class) filterClass; - typeFilters.add(new AnnotationTypeFilter(annotationType)); - } - case ASSIGNABLE_TYPE -> typeFilters.add(new AssignableTypeFilter(filterClass)); - case CUSTOM -> { - Assert.isAssignable(TypeFilter.class, filterClass, - "An error occurred while processing a @IntegrationComponentScan CUSTOM type filter: "); - TypeFilter typeFilter = BeanUtils.instantiateClass(filterClass, TypeFilter.class); - invokeAwareMethods(filter, this.environment, this.resourceLoader, registry); - typeFilters.add(typeFilter); - } - default -> throw new IllegalArgumentException("Filter type not supported with Class value: " + - filterType); - } - } - - for (String expression : filter.getStringArray("pattern")) { - switch (filterType) { - case ASPECTJ -> - typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader())); - case REGEX -> typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression))); - default -> throw new IllegalArgumentException("Filter type not supported with String pattern: " - + filterType); - } - } - - return typeFilters; - } - - private static void invokeAwareMethods(Object parserStrategyBean, Environment environment, - ResourceLoader resourceLoader, BeanDefinitionRegistry registry) { - - if (parserStrategyBean instanceof Aware) { - if (parserStrategyBean instanceof BeanClassLoaderAware) { - ClassLoader classLoader = - registry instanceof ConfigurableBeanFactory - ? ((ConfigurableBeanFactory) registry).getBeanClassLoader() - : resourceLoader.getClassLoader(); - if (classLoader != null) { - ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader); - } - } - if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) { - ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry); - } - if (parserStrategyBean instanceof EnvironmentAware) { - ((EnvironmentAware) parserStrategyBean).setEnvironment(environment); - } - if (parserStrategyBean instanceof ResourceLoaderAware) { - ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader); - } - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigUtils.java deleted file mode 100644 index 355b95a7e30..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.springframework.beans.factory.config.SingletonBeanRegistry; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.annotation.AnnotationBeanNameGenerator; -import org.springframework.context.annotation.AnnotationConfigUtils; -import org.springframework.integration.channel.DirectChannel; - -/** - * Shared utility methods for Integration configuration. - * - * @author Artem Bilan - * @author Chris Bono - * - * @since 4.0 - */ -public final class IntegrationConfigUtils { - - public static final String HANDLER_ALIAS_SUFFIX = ".handler"; - - public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className, - String methodSignature) { - - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class) - .addConstructorArgValue(className) - .addConstructorArgValue(methodSignature); - registry.registerBeanDefinition(functionId, builder.getBeanDefinition()); - } - - /** - * Register a {@link SpelFunctionFactoryBean} for the provided method signature. - * @param registry the registry for bean to register - * @param functionId the bean name - * @param aClass the class for function - * @param methodSignature the function method to be called from SpEL - * @since 5.5 - */ - public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, Class aClass, - String methodSignature) { - - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class) - .addConstructorArgValue(aClass) - .addConstructorArgValue(methodSignature); - registry.registerBeanDefinition(functionId, builder.getBeanDefinition()); - } - - public static void autoCreateDirectChannel(String channelName, BeanDefinitionRegistry registry) { - registry.registerBeanDefinition(channelName, new RootBeanDefinition(DirectChannel.class)); - } - - public static BeanNameGenerator annotationBeanNameGenerator(BeanDefinitionRegistry registry) { - BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE; - if (registry instanceof SingletonBeanRegistry singletonBeanRegistry) { - BeanNameGenerator generator = - (BeanNameGenerator) singletonBeanRegistry.getSingleton( - AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); - if (generator != null) { - beanNameGenerator = generator; - } - } - - return beanNameGenerator; - } - - private IntegrationConfigUtils() { - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationBeanFactoryPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationBeanFactoryPostProcessor.java deleted file mode 100644 index 663692a76ae..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationBeanFactoryPostProcessor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.List; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.core.io.support.SpringFactoriesLoader; - -/** - * {@link BeanDefinitionRegistryPostProcessor} to apply external Integration - * infrastructure configurations via loading {@link IntegrationConfigurationInitializer} - * implementations using {@link SpringFactoriesLoader}. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.0 - */ -public class IntegrationConfigurationBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; - - List initializers = - SpringFactoriesLoader.loadFactories(IntegrationConfigurationInitializer.class, - beanFactory.getBeanClassLoader()); - - for (IntegrationConfigurationInitializer initializer : initializers) { - initializer.initialize(beanFactory); - } - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationInitializer.java deleted file mode 100644 index 6c5c751d897..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationInitializer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; - -/** - * The strategy to initialize the external Integration infrastructure (@{code BeanFactoryPostProcessor}s, - * global beans etc.) in the provided {@code beanFactory}. - *

- * Typically implementations are loaded by {@link org.springframework.core.io.support.SpringFactoriesLoader}. - * - * @author Artem Bilan - * @since 4.0 - */ -@FunctionalInterface -public interface IntegrationConfigurationInitializer { - - void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException; - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationReport.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationReport.java deleted file mode 100644 index 5a18528beff..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigurationReport.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Properties; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.context.IntegrationContextUtils; - -/** - * An {@link org.springframework.beans.factory.config.BeanDefinition#ROLE_INFRASTRUCTURE} - * component to report Spring Integration relevant configuration state after application context is refreshed. - * - * @author Artem Bilan - * - * @since 6.2 - */ -class IntegrationConfigurationReport - implements ApplicationContextAware, ApplicationListener { - - private static final LogAccessor LOGGER = new LogAccessor(IntegrationConfigurationReport.class); - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - if (event.getApplicationContext().equals(this.applicationContext)) { - report(); - } - } - - private void report() { - printIntegrationProperties(); - } - - private void printIntegrationProperties() { - if (LOGGER.isDebugEnabled()) { - Properties integrationProperties = - IntegrationContextUtils.getIntegrationProperties(this.applicationContext) - .toProperties(); - - StringWriter writer = new StringWriter(); - integrationProperties.list(new PrintWriter(writer)); - StringBuffer propertiesBuffer = writer.getBuffer() - .delete(0, "-- listing properties --".length()); - LOGGER.debug("\nSpring Integration global properties:\n" + propertiesBuffer); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java deleted file mode 100644 index 96bdfad4f4b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -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; - -/** - * A marker annotation (an analogue of {@code }) to register - * {@link org.springframework.core.convert.converter.Converter}, - * {@link org.springframework.core.convert.converter.GenericConverter} or - * {@link org.springframework.core.convert.converter.ConverterFactory} beans for the {@code integrationConversionService}. - *

- * This annotation can be used at the {@code class} level for {@link org.springframework.stereotype.Component} beans - * and on methods with {@link org.springframework.context.annotation.Bean}. - * - * @author Artem Bilan - * @since 4.0 - */ -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IntegrationConverter { - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java deleted file mode 100644 index e765aff5d47..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.MethodMetadata; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.support.utils.IntegrationUtils; - -/** - * The {@link IntegrationConfigurationInitializer} to populate - * {@link ConverterRegistrar.IntegrationConverterRegistration} - * for converter beans marked with an {@link IntegrationConverter} annotation. - *

- * {@link org.springframework.context.annotation.Bean} methods are also processed. - * - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * - * @since 4.0 - */ -public class IntegrationConverterInitializer implements IntegrationConfigurationInitializer { - - @Override - public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - - for (String beanName : registry.getBeanDefinitionNames()) { - BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); - if (isIntegrationConverter(beanDefinition)) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition( - ConverterRegistrar.IntegrationConverterRegistration.class) - .addConstructorArgReference(beanName); - BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); - } - } - - if (!registry.containsBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME)) { - registry.registerBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME, - new RootBeanDefinition(ConverterRegistrar.class)); - } - - if (!registry.containsBeanDefinition(IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME)) { - RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomConversionServiceFactoryBean.class); - beanDefinition.setAutowireCandidate(false); - registry.registerBeanDefinition(IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, - beanDefinition); - } - } - - private static boolean isIntegrationConverter(BeanDefinition beanDefinition) { - boolean hasIntegrationConverter = false; - if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { - AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata(); - hasIntegrationConverter = metadata.hasAnnotation(IntegrationConverter.class.getName()); - if (!hasIntegrationConverter && beanDefinition.getSource() instanceof MethodMetadata beanMethodMetadata) { - hasIntegrationConverter = beanMethodMetadata.isAnnotated(IntegrationConverter.class.getName()); - } - } - return hasIntegrationConverter; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java deleted file mode 100644 index d587d6c4174..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Method; -import java.util.Map.Entry; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.context.expression.MapAccessor; -import org.springframework.expression.BeanResolver; -import org.springframework.expression.IndexAccessor; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.expression.spel.support.StandardTypeLocator; -import org.springframework.integration.context.IntegrationContextUtils; - -/** - *

- * {@link FactoryBean} to populate {@link StandardEvaluationContext} instances enhanced with: - *

    - *
  • - * a {@link BeanFactoryResolver}. - *
  • - *
  • - * a {@link org.springframework.expression.TypeConverter} based on the - * {@link org.springframework.core.convert.ConversionService} from the application context. - *
  • - *
  • - * a set of provided {@link PropertyAccessor}s including a default {@link MapAccessor}. - *
  • - *
  • - * a set of provided SpEL functions. - *
  • - *
- *

- * After initialization this factory populates functions and property accessors from - * {@link SpelFunctionFactoryBean}s and - * {@link org.springframework.integration.expression.SpelPropertyAccessorRegistrar}, - * respectively. - * Functions and property accessors are also inherited from any parent context. - *

- *

- * This factory returns a new instance for each reference - {@link #isSingleton()} returns false. - *

- * - * @author Artem Bilan - * @author Gary Russell - * - * @since 3.0 - */ -public class IntegrationEvaluationContextFactoryBean extends AbstractEvaluationContextFactoryBean - implements FactoryBean { - - @Nullable - private TypeLocator typeLocator; - - @SuppressWarnings("NullAway.Init") - private BeanResolver beanResolver; - - public void setTypeLocator(TypeLocator typeLocator) { - this.typeLocator = typeLocator; - } - - @Override - public boolean isSingleton() { - return false; - } - - @Override - public void afterPropertiesSet() { - ApplicationContext applicationContext = getApplicationContext(); - if (applicationContext != null) { - this.beanResolver = new BeanFactoryResolver(applicationContext); - if (this.typeLocator == null) { - this.typeLocator = new StandardTypeLocator(applicationContext.getClassLoader()); - } - } - initialize(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME); - } - - @Override - public StandardEvaluationContext getObject() { - StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - if (this.typeLocator != null) { - evaluationContext.setTypeLocator(this.typeLocator); - } - - evaluationContext.setBeanResolver(this.beanResolver); - evaluationContext.setTypeConverter(getTypeConverter()); - - for (PropertyAccessor propertyAccessor : getPropertyAccessors().values()) { - evaluationContext.addPropertyAccessor(propertyAccessor); - } - - evaluationContext.addPropertyAccessor(new MapAccessor()); - - for (IndexAccessor indexAccessor : getIndexAccessors().values()) { - evaluationContext.addIndexAccessor(indexAccessor); - } - - for (Entry functionEntry : getFunctions().entrySet()) { - evaluationContext.registerFunction(functionEntry.getKey(), functionEntry.getValue()); - } - - return evaluationContext; - } - - @Override - public Class getObjectType() { - return StandardEvaluationContext.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java deleted file mode 100644 index d1cdb6d2ae9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import io.micrometer.observation.ObservationRegistry; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.EnvironmentAware; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportAware; -import org.springframework.context.annotation.Role; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.integration.support.management.ControlBusCommandRegistry; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * {@code @Configuration} class that registers a {@link IntegrationManagementConfigurer} bean. - * - *

This configuration class is automatically imported when using the - * {@link EnableIntegrationManagement} annotation. See its javadoc for complete usage details. - * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.2 - */ -@Configuration(proxyBeanMethods = false) -@Role(BeanDefinition.ROLE_INFRASTRUCTURE) -public class IntegrationManagementConfiguration implements ImportAware, EnvironmentAware { - - private final ControlBusCommandRegistry controlBusCommandRegistry; - - private @Nullable AnnotationAttributes attributes; - - @SuppressWarnings("NullAway.Init") - private Environment environment; - - public IntegrationManagementConfiguration(ControlBusCommandRegistry controlBusCommandRegistry) { - this.controlBusCommandRegistry = controlBusCommandRegistry; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - @Override - public void setImportMetadata(AnnotationMetadata importMetadata) { - Map map = Objects.requireNonNull(importMetadata.getAnnotationAttributes(EnableIntegrationManagement.class.getName())); - this.attributes = AnnotationAttributes.fromMap(map); - Assert.notNull(this.attributes, () -> - "@EnableIntegrationManagement is not present on importing class " + importMetadata.getClassName()); - this.controlBusCommandRegistry.setEagerInitialization( - Boolean.parseBoolean( - this.environment.resolvePlaceholders(this.attributes.getString("loadControlBusCommands")))); - } - - @Bean(name = IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public IntegrationManagementConfigurer managementConfigurer( - ObjectProvider metricsCaptorProvider, - ObjectProvider observationRegistryProvider) { - - IntegrationManagementConfigurer configurer = new IntegrationManagementConfigurer(); - String defaultLoggingEnabled = (String) Objects.requireNonNull(this.attributes).get("defaultLoggingEnabled"); - configurer.setDefaultLoggingEnabled( - Boolean.parseBoolean(this.environment.resolvePlaceholders(defaultLoggingEnabled == null - ? "false" : defaultLoggingEnabled))); - configurer.setMetricsCaptorProvider(metricsCaptorProvider); - String[] observationPatterns = obtainObservationPatterns(); - if (observationPatterns.length > 0) { - configurer.setObservationPatterns(observationPatterns); - configurer.setObservationRegistry(observationRegistryProvider); - } - return configurer; - } - - private String[] obtainObservationPatterns() { - Collection observationPatterns = new HashSet<>(); - String[] patternsProperties = (String[]) Objects.requireNonNull(this.attributes).get("observationPatterns"); - patternsProperties = patternsProperties == null ? new String[0] : patternsProperties; - boolean hasAsterisk = false; - for (String patternProperty : patternsProperties) { - String patternValue = this.environment.resolvePlaceholders(patternProperty); - String[] patternsToProcess = StringUtils.commaDelimitedListToStringArray(patternValue); - for (String pattern : patternsToProcess) { - hasAsterisk |= "*".equals(pattern); - if (StringUtils.hasText(pattern) && (pattern.startsWith("!") || !hasAsterisk)) { - observationPatterns.add(pattern); - } - } - } - if (hasAsterisk) { - observationPatterns = - observationPatterns.stream() - .filter((pattern) -> pattern.startsWith("!")) - .collect(Collectors.toList()); - - observationPatterns.add("*"); - } - return observationPatterns.toArray(new String[0]); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java deleted file mode 100644 index 8401e03a409..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import io.micrometer.observation.ObservationRegistry; -import org.apache.commons.logging.Log; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextClosedEvent; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.support.management.IntegrationManagement; -import org.springframework.integration.support.management.IntegrationManagement.ManagementOverrides; -import org.springframework.integration.support.management.metrics.MeterFacade; -import org.springframework.integration.support.management.metrics.MetricsCaptor; -import org.springframework.integration.support.utils.PatternMatchUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; - -/** - * Configures beans that implement {@link IntegrationManagement}. - * Configures logging, {@link MetricsCaptor} and {@link ObservationRegistry} for all (or selected) components. - * - * @author Gary Russell - * @author Artem Bilan - * @author Meherzad Lahewala - * @author Jonathan Pearlin - * - * @since 4.2 - * - */ -public class IntegrationManagementConfigurer - implements SmartInitializingSingleton, ApplicationContextAware, BeanNameAware, BeanPostProcessor, - ApplicationListener { - - /** - * Bean name of the configurer. - */ - public static final String MANAGEMENT_CONFIGURER_NAME = "integrationManagementConfigurer"; - - private final Set gauges = new HashSet<>(); - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - private boolean defaultLoggingEnabled = true; - - private volatile boolean singletonsInstantiated; - - private @Nullable MetricsCaptor metricsCaptor; - - private @Nullable ObjectProvider metricsCaptorProvider; - - private @Nullable ObservationRegistry observationRegistry; - - private @Nullable ObjectProvider observationRegistryProvider; - - @SuppressWarnings("NullAway.Init") //Creation is handled in the postProcessAfterInitialization - private String[] observationPatterns; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void setBeanName(String name) { - this.beanName = name; - } - - /** - * Disable all logging in the normal message flow in framework components. When 'false', such logging will be - * skipped, regardless of logging level. When 'true', the logging is controlled as normal by the logging - * subsystem log level configuration. - *

- * Exception logging (debug or otherwise) is not affected by this setting. - *

- * It has been found that in high-volume messaging environments, calls to methods such as - * {@link Log#isDebugEnabled()} can be quite expensive - * and account for an inordinate amount of CPU time. - *

- * Set this to 'false' to disable logging by default in all framework components that implement - * {@link IntegrationManagement} (channels, message handlers etc.) This turns off logging such as - * "PreSend on channel", "Received message" etc. - *

- * After the context is initialized, individual components can have their setting changed by invoking - * {@link IntegrationManagement#setLoggingEnabled(boolean)}. - * @param defaultLoggingEnabled defaults to true. - */ - public void setDefaultLoggingEnabled(boolean defaultLoggingEnabled) { - this.defaultLoggingEnabled = defaultLoggingEnabled; - } - - public void setMetricsCaptor(@Nullable MetricsCaptor metricsCaptor) { - this.metricsCaptor = metricsCaptor; - } - - void setMetricsCaptorProvider(ObjectProvider metricsCaptorProvider) { - this.metricsCaptorProvider = metricsCaptorProvider; - } - - /** - * Set an {@link ObservationRegistry} to populate to the {@link IntegrationManagement} components - * in the application context. - * @param observationRegistry the {@link ObservationRegistry} to use. - * @since 6.0 - */ - public void setObservationRegistry(@Nullable ObservationRegistry observationRegistry) { - this.observationRegistry = observationRegistry; - } - - void setObservationRegistry(ObjectProvider observationRegistryProvider) { - this.observationRegistryProvider = observationRegistryProvider; - } - - /** - * Set simple patterns for component names matching which has to be instrumented with a {@link ObservationRegistry}. - * @param observationPatterns the simple patterns to use. - * @since 6.0 - * @see PatternMatchUtils#smartMatch(String, String...) - */ - public void setObservationPatterns(String... observationPatterns) { - Assert.notEmpty(observationPatterns, "'observationPatterns' must not be empty"); - this.observationPatterns = Arrays.copyOf(observationPatterns, observationPatterns.length); - } - - @Override - public void afterSingletonsInstantiated() { - Assert.state(this.applicationContext != null, "'applicationContext' must not be null"); - Assert.state(MANAGEMENT_CONFIGURER_NAME.equals(this.beanName), getClass().getSimpleName() - + " bean name must be " + MANAGEMENT_CONFIGURER_NAME); - - MetricsCaptor metricsCaptorToUse = obtainMetricsCaptor(); - if (metricsCaptorToUse != null) { - registerComponentGauges(metricsCaptorToUse); - } - - setupObservationRegistry(); - - this.applicationContext.getBeansOfType(IntegrationManagement.class).values() - .forEach(this::enhanceIntegrationManagement); - - this.singletonsInstantiated = true; - } - - @Nullable - private MetricsCaptor obtainMetricsCaptor() { - if (this.metricsCaptor == null && this.metricsCaptorProvider != null) { - this.metricsCaptor = this.metricsCaptorProvider.getIfUnique(); - } - return this.metricsCaptor; - } - - private void setupObservationRegistry() { - if (this.observationRegistry == null && this.observationRegistryProvider != null) { - this.observationRegistry = this.observationRegistryProvider.getIfUnique(); - } - } - - private void registerComponentGauges(MetricsCaptor metricsCaptor) { - this.gauges.add( - metricsCaptor.gaugeBuilder("spring.integration.channels", this, - (c) -> this.applicationContext.getBeansOfType(MessageChannel.class).size()) - .description("The number of message channels") - .build()); - - this.gauges.add( - metricsCaptor.gaugeBuilder("spring.integration.handlers", this, - (c) -> this.applicationContext.getBeansOfType(MessageHandler.class).size()) - .description("The number of message handlers") - .build()); - - this.gauges.add( - metricsCaptor.gaugeBuilder("spring.integration.sources", this, - (c) -> this.applicationContext.getBeansOfType(MessageSource.class).size()) - .description("The number of message sources") - .build()); - } - - private void enhanceIntegrationManagement(IntegrationManagement integrationManagement) { - if (!getOverrides(integrationManagement).loggingConfigured) { - integrationManagement.setLoggingEnabled(this.defaultLoggingEnabled); - } - if (this.metricsCaptor != null) { - integrationManagement.registerMetricsCaptor(this.metricsCaptor); - } - if (this.observationRegistry != null && - Boolean.TRUE.equals(PatternMatchUtils.smartMatch( - integrationManagement.getComponentName(), this.observationPatterns))) { - - integrationManagement.registerObservationRegistry(this.observationRegistry); - } - } - - @Override - public Object postProcessAfterInitialization(Object bean, String name) throws BeansException { - if (this.singletonsInstantiated && bean instanceof IntegrationManagement integrationManagement) { - enhanceIntegrationManagement(integrationManagement); - } - return bean; - } - - @Override - public void onApplicationEvent(ContextClosedEvent event) { - if (event.getApplicationContext().equals(this.applicationContext)) { - this.gauges.forEach(MeterFacade::remove); - this.gauges.clear(); - } - } - - private static ManagementOverrides getOverrides(IntegrationManagement bean) { - return bean.getOverrides() != null ? bean.getOverrides() : new ManagementOverrides(); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java deleted file mode 100644 index df1d390f6b1..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.beans.Introspector; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.ApplicationContextException; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.util.ClassUtils; - -/** - * {@link ImportBeanDefinitionRegistrar} implementation that configures integration infrastructure. - * - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * - * @since 4.0 - */ -public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar { - - static { - if (ClassUtils.isPresent("org.springframework.integration.dsl.support.Function", null)) { - throw new ApplicationContextException("Starting with Spring Integration 5.0, " - + "the 'spring-integration-java-dsl' dependency is no longer needed; " - + "the Java DSL has been merged into the core project. " - + "If it is present on the classpath, it will cause class loading conflicts."); - } - } - - /** - * Invoked by the framework when an @EnableIntegration annotation is encountered. - * Also called with {@code null} {@code importingClassMetadata} from {@code AbstractIntegrationNamespaceHandler} - * to register the same beans when using XML configuration. Also called by {@code AnnotationConfigParser} - * to register the messaging annotation post processors (for {@code }). - */ - @Override - public void registerBeanDefinitions(@Nullable AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - - // Ensure that ClassUtils is initialized with a proper Spring application context ClassLoader. - org.springframework.integration.util.ClassUtils.resolvePrimitiveType(Integer.class); - - registerDefaultConfiguringBeanFactoryPostProcessor(registry); - registerIntegrationConfigurationBeanFactoryPostProcessor(registry); - if (importingClassMetadata != null) { - registerMessagingAnnotationPostProcessors(registry); - } - registerGatewayProxyInstantiationPostProcessor(registry); - } - - /** - * Register {@code DefaultConfiguringBeanFactoryPostProcessor}, if necessary. - * @param registry The {@link BeanDefinitionRegistry} to register additional {@link BeanDefinition}s. - */ - private void registerDefaultConfiguringBeanFactoryPostProcessor(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(IntegrationContextUtils.DEFAULT_CONFIGURING_POSTPROCESSOR_BEAN_NAME)) { - BeanDefinitionBuilder postProcessorBuilder = - BeanDefinitionBuilder.genericBeanDefinition(DefaultConfiguringBeanFactoryPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(IntegrationContextUtils.DEFAULT_CONFIGURING_POSTPROCESSOR_BEAN_NAME, - postProcessorBuilder.getBeanDefinition()); - } - } - - /** - * Register {@link IntegrationConfigurationBeanFactoryPostProcessor} - * to process the external Integration infrastructure. - */ - private void registerIntegrationConfigurationBeanFactoryPostProcessor(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition( - IntegrationContextUtils.INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME)) { - - BeanDefinitionBuilder postProcessorBuilder = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationConfigurationBeanFactoryPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME, - postProcessorBuilder.getBeanDefinition()); - } - } - - /** - * Register {@link MessagingAnnotationPostProcessor} and - * {@link MessagingAnnotationBeanPostProcessor}, - * if necessary. - * @param registry The {@link BeanDefinitionRegistry} to register additional {@link BeanDefinition}s. - * @see MessagingAnnotationPostProcessor#messagingAnnotationBeanPostProcessor() - */ - private void registerMessagingAnnotationPostProcessors(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME)) { - registry.registerBeanDefinition(IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME, - BeanDefinitionBuilder.genericBeanDefinition(MessagingAnnotationPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) - .getBeanDefinition()); - } - - String beanName = Introspector.decapitalize(MessagingAnnotationBeanPostProcessor.class.getName()); - if (!registry.containsBeanDefinition(beanName)) { - registry.registerBeanDefinition(beanName, - BeanDefinitionBuilder.genericBeanDefinition() - .setFactoryMethodOnBean("messagingAnnotationBeanPostProcessor", - IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) - .getBeanDefinition()); - } - } - - private void registerGatewayProxyInstantiationPostProcessor(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition("gatewayProxyBeanDefinitionPostProcessor")) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyInstantiationPostProcessor.class) - .addConstructorArgValue(registry) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - registry.registerBeanDefinition("gatewayProxyBeanDefinitionPostProcessor", builder.getBeanDefinition()); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationSimpleEvaluationContextFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationSimpleEvaluationContextFactoryBean.java deleted file mode 100644 index a0d7aeb01e9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationSimpleEvaluationContextFactoryBean.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map.Entry; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.context.expression.MapAccessor; -import org.springframework.expression.IndexAccessor; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.spel.support.DataBindingPropertyAccessor; -import org.springframework.expression.spel.support.SimpleEvaluationContext; -import org.springframework.integration.context.IntegrationContextUtils; - -/** - *

- * {@link FactoryBean} to populate {@link SimpleEvaluationContext} instances enhanced with: - *

    - *
  • - * a {@link org.springframework.expression.TypeConverter} based on the - * {@link org.springframework.core.convert.ConversionService} - * from the application context. - *
  • - *
  • - * a set of provided {@link PropertyAccessor}s including a default {@link MapAccessor}. - *
  • - *
  • - * a set of provided SpEL functions. - *
  • - *
- *

- * After initialization this factory populates functions and property accessors from - * {@link SpelFunctionFactoryBean}s and - * {@link org.springframework.integration.expression.SpelPropertyAccessorRegistrar}, - * respectively. - * Functions and property accessors are also inherited from any parent context. - *

- *

- * This factory returns a new instance for each reference - {@link #isSingleton()} returns false. - *

- * - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.3.15 - */ -public class IntegrationSimpleEvaluationContextFactoryBean extends AbstractEvaluationContextFactoryBean - implements FactoryBean { - - @Override - public boolean isSingleton() { - return false; - } - - @Override - public void afterPropertiesSet() { - initialize(IntegrationContextUtils.INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME); - } - - @Override - public SimpleEvaluationContext getObject() { - Collection accessors = getPropertyAccessors().values(); - PropertyAccessor[] accessorArray = accessors.toArray(new PropertyAccessor[accessors.size() + 2]); - accessorArray[accessors.size()] = new MapAccessor(); - accessorArray[accessors.size() + 1] = DataBindingPropertyAccessor.forReadOnlyAccess(); - SimpleEvaluationContext evaluationContext = - SimpleEvaluationContext.forPropertyAccessors(accessorArray) - .withIndexAccessors(getIndexAccessors().values().toArray(new IndexAccessor[0])) - .withTypeConverter(getTypeConverter()) - .withInstanceMethods() - .withAssignmentDisabled() - .build(); - for (Entry functionEntry : getFunctions().entrySet()) { - evaluationContext.setVariable(functionEntry.getKey(), functionEntry.getValue()); - } - return evaluationContext; - } - - @Override - public Class getObjectType() { - return SimpleEvaluationContext.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessageHistoryRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageHistoryRegistrar.java deleted file mode 100644 index c6d1163c27d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/MessageHistoryRegistrar.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Map; -import java.util.Objects; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.history.MessageHistoryConfigurer; - -/** - * Registers the {@link MessageHistoryConfigurer} {@link org.springframework.beans.factory.config.BeanDefinition} - * for {@link org.springframework.integration.history.MessageHistory}. - * This registrar is applied from {@code @EnableMessageHistory} on the {@code Configuration} class - * or from {@code MessageHistoryParser}. - * - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * - * @since 4.0 - */ -public class MessageHistoryRegistrar implements ImportBeanDefinitionRegistrar { - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME)) { - throw new BeanDefinitionStoreException( - "Only one @EnableMessageHistory or can be declared in the application context."); - } - - Map annotationAttributes = - importingClassMetadata.getAnnotationAttributes(EnableMessageHistory.class.getName()); - Object componentNamePatterns = Objects.requireNonNull(annotationAttributes).get("value"); // NOSONAR never null - - String patterns; - - if (componentNamePatterns instanceof String[]) { - patterns = String.join(",", (String[]) componentNamePatterns); - } - else { - patterns = (String) componentNamePatterns; - } - - BeanDefinition messageHistoryConfigurer = - BeanDefinitionBuilder.genericBeanDefinition(MessageHistoryConfigurer.class) - .addPropertyValue("componentNamePatterns", patterns) - .getBeanDefinition(); - - registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME, - messageHistoryConfigurer); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingAnnotationBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingAnnotationBeanPostProcessor.java deleted file mode 100644 index 99793138a98..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingAnnotationBeanPostProcessor.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2023-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.integration.annotation.EndpointId; -import org.springframework.integration.annotation.Role; -import org.springframework.integration.config.annotation.MethodAnnotationPostProcessor; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - -/** - * An infrastructure {@link BeanPostProcessor} implementation that processes method-level - * messaging annotations such as @Transformer, @Splitter, @Router, and @Filter. - * - * @author Artem Bilan - * - * @since 6.2 - */ -public class MessagingAnnotationBeanPostProcessor - implements BeanPostProcessor, BeanFactoryAware, SmartInitializingSingleton { - - private final Set> noAnnotationsCache = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); - - private final Map, MethodAnnotationPostProcessor> postProcessors; - - private final List methodsToPostProcessAfterContextInitialization = new ArrayList<>(); - - @SuppressWarnings("NullAway.Init") - private ConfigurableListableBeanFactory beanFactory; - - private volatile boolean initialized; - - public MessagingAnnotationBeanPostProcessor( - Map, MethodAnnotationPostProcessor> postProcessors) { - - this.postProcessors = postProcessors; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; - } - - @Override - public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { - Assert.notNull(this.beanFactory, "BeanFactory must not be null"); - - Class beanClass = AopUtils.getTargetClass(bean); - - // the set will hold records of prior class scans and indicate if no messaging annotations were found - if (this.noAnnotationsCache.contains(beanClass)) { - return bean; - } - - ReflectionUtils.doWithMethods(beanClass, - method -> doWithMethod(method, bean, beanName, beanClass), - ReflectionUtils.USER_DECLARED_METHODS); - - return bean; - } - - private void doWithMethod(Method method, Object bean, String beanName, Class beanClass) { - MergedAnnotations mergedAnnotations = MergedAnnotations.from(method); - - boolean noMessagingAnnotations = true; - - // See MessagingAnnotationPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) - if (!mergedAnnotations.isPresent(Bean.class)) { - List messagingAnnotations = - obtainMessagingAnnotations(this.postProcessors.keySet(), mergedAnnotations, - method.toGenericString()); - - for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) { - noMessagingAnnotations = false; - Class annotationType = messagingAnnotation.annotationType; - List annotationChain = - MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation, annotationType); - processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain); - } - } - if (noMessagingAnnotations) { - this.noAnnotationsCache.add(beanClass); - } - } - - private void processAnnotationTypeOnMethod(Object bean, String beanName, Method method, - Class annotationType, List annotations) { - - MethodAnnotationPostProcessor postProcessor = this.postProcessors.get(annotationType); - if (postProcessor != null && postProcessor.supportsPojoMethod() - && postProcessor.shouldCreateEndpoint(method, annotations)) { - - Method targetMethod = method; - if (AopUtils.isJdkDynamicProxy(bean)) { - try { - targetMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); - } - catch (NoSuchMethodException e) { - throw new IllegalArgumentException("Service methods must be extracted to the service " - + "interface for JdkDynamicProxy. The affected bean is: '" + beanName + "' " - + "and its method: '" + method + "'", e); - } - } - - if (this.initialized) { - postProcessMethodAndRegisterEndpointIfAny(bean, beanName, method, annotationType, annotations, - postProcessor, targetMethod); - } - else { - Method methodToPostProcess = targetMethod; - this.methodsToPostProcessAfterContextInitialization.add(() -> - postProcessMethodAndRegisterEndpointIfAny(bean, beanName, method, annotationType, annotations, - postProcessor, methodToPostProcess)); - } - } - } - - @SuppressWarnings("unchecked") - private void postProcessMethodAndRegisterEndpointIfAny(Object bean, String beanName, Method method, - Class annotationType, List annotations, - MethodAnnotationPostProcessor postProcessor, Method targetMethod) { - - Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations); - if (result instanceof AbstractEndpoint endpoint) { - String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup", String.class); - if (StringUtils.hasText(autoStartup)) { - autoStartup = this.beanFactory.resolveEmbeddedValue(autoStartup); - if (StringUtils.hasText(autoStartup)) { - endpoint.setAutoStartup(Boolean.parseBoolean(autoStartup)); - } - } - - String phase = MessagingAnnotationUtils.resolveAttribute(annotations, "phase", String.class); - if (StringUtils.hasText(phase)) { - phase = this.beanFactory.resolveEmbeddedValue(phase); - if (StringUtils.hasText(phase)) { - endpoint.setPhase(Integer.parseInt(phase)); - } - } - - Role role = AnnotationUtils.findAnnotation(method, Role.class); - if (role != null) { - endpoint.setRole(role.value()); - } - - String endpointBeanName = generateBeanName(beanName, method, annotationType); - endpoint.setBeanName(endpointBeanName); - ((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(endpointBeanName, - new RootBeanDefinition((Class) endpoint.getClass(), () -> endpoint)); - this.beanFactory.getBean(endpointBeanName); - } - } - - protected String generateBeanName(String originalBeanName, Method method, - Class annotationType) { - - String name = MessagingAnnotationUtils.endpointIdValue(method); - if (!StringUtils.hasText(name)) { - String baseName = originalBeanName + "." + method.getName() + "." - + ClassUtils.getShortNameAsProperty(annotationType); - name = baseName; - int count = 1; - while (this.beanFactory.containsBean(name)) { - name = baseName + "#" + (++count); - } - } - return name; - } - - @Override - public void afterSingletonsInstantiated() { - this.initialized = true; - this.methodsToPostProcessAfterContextInitialization.forEach(Runnable::run); - this.methodsToPostProcessAfterContextInitialization.clear(); - } - - protected static List obtainMessagingAnnotations( - Set> postProcessors, MergedAnnotations annotations, String identified) { - - List messagingAnnotations = new ArrayList<>(); - - for (Class annotationType : postProcessors) { - annotations.stream() - .filter((ann) -> ann.getType().equals(annotationType)) - .map(MergedAnnotation::getRoot) - .map(MergedAnnotation::synthesize) - .map((ann) -> new MessagingMetaAnnotation(ann, annotationType)) - .forEach(messagingAnnotations::add); - } - - if (annotations.get(EndpointId.class, (ann) -> ann.hasNonDefaultValue("value")).isPresent() - && messagingAnnotations.size() > 1) { - - throw new IllegalStateException("@EndpointId on " + identified - + " can only have one EIP annotation, found: " + messagingAnnotations.size()); - } - - return messagingAnnotations; - } - - public record MessagingMetaAnnotation(Annotation annotation, Class annotationType) { - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingAnnotationPostProcessor.java deleted file mode 100644 index ed53ebe8d0c..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingAnnotationPostProcessor.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.BeanDefinitionValidationException; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.type.MethodMetadata; -import org.springframework.core.type.StandardMethodMetadata; -import org.springframework.integration.annotation.Aggregator; -import org.springframework.integration.annotation.BridgeFrom; -import org.springframework.integration.annotation.BridgeTo; -import org.springframework.integration.annotation.Filter; -import org.springframework.integration.annotation.InboundChannelAdapter; -import org.springframework.integration.annotation.Router; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.annotation.Splitter; -import org.springframework.integration.annotation.Transformer; -import org.springframework.integration.config.annotation.MethodAnnotationPostProcessor; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.util.CollectionUtils; - -/** - * A {@link BeanPostProcessor} implementation that processes method-level - * messaging annotations such as @Transformer, @Splitter, @Router, and @Filter. - * - * @author Mark Fisher - * @author Marius Bogoevici - * @author Artem Bilan - * @author Gary Russell - * @author Rick Hogge - */ -public class MessagingAnnotationPostProcessor implements BeanDefinitionRegistryPostProcessor { - - private final Map, MethodAnnotationPostProcessor> postProcessors = new HashMap<>(); - - @SuppressWarnings("NullAway.Init") - private BeanDefinitionRegistry registry; - - @SuppressWarnings("NullAway.Init") - private ConfigurableListableBeanFactory beanFactory; - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - this.registry = registry; - - this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor()); - this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor()); - this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor()); - this.postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor()); - this.postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor()); - this.postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor()); - this.postProcessors.put(InboundChannelAdapter.class, new InboundChannelAdapterAnnotationPostProcessor()); - this.postProcessors.put(BridgeFrom.class, new BridgeFromAnnotationPostProcessor()); - this.postProcessors.put(BridgeTo.class, new BridgeToAnnotationPostProcessor()); - - Map, MethodAnnotationPostProcessor> customPostProcessors = - setupCustomPostProcessors(); - if (!CollectionUtils.isEmpty(customPostProcessors)) { - this.postProcessors.putAll(customPostProcessors); - } - this.postProcessors.values().stream() - .filter(BeanFactoryAware.class::isInstance) - .map(BeanFactoryAware.class::cast) - .forEach((processor) -> processor.setBeanFactory((BeanFactory) this.registry)); - - String[] beanNames = registry.getBeanDefinitionNames(); - - for (String beanName : beanNames) { - BeanDefinition beanDef = registry.getBeanDefinition(beanName); - if (beanDef instanceof AnnotatedBeanDefinition annotatedBeanDefinition - && annotatedBeanDefinition.getFactoryMethodMetadata() != null) { - - processCandidate(beanName, annotatedBeanDefinition); - } - } - } - - /** - * The factory method for {@link MessagingAnnotationBeanPostProcessor} based - * on the environment from this {@link MessagingAnnotationPostProcessor}. - * @return the {@link MessagingAnnotationBeanPostProcessor} instance based on {@link #postProcessors}. - * @since 6.2 - */ - public MessagingAnnotationBeanPostProcessor messagingAnnotationBeanPostProcessor() { - return new MessagingAnnotationBeanPostProcessor(this.postProcessors); - } - - private void processCandidate(String beanName, AnnotatedBeanDefinition beanDefinition) { - MethodMetadata methodMetadata = beanDefinition.getFactoryMethodMetadata(); - MergedAnnotations annotations = Objects.requireNonNull(methodMetadata).getAnnotations(); - if (methodMetadata instanceof StandardMethodMetadata standardMethodMetadata) { - annotations = MergedAnnotations.from(standardMethodMetadata.getIntrospectedMethod()); - } - - List messagingAnnotations = - MessagingAnnotationBeanPostProcessor.obtainMessagingAnnotations(this.postProcessors.keySet(), - annotations, beanName); - - for (MessagingAnnotationBeanPostProcessor.MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) { - Class annotationType = messagingAnnotation.annotationType(); - List annotationChain = - MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation(), annotationType); - - processMessagingAnnotationOnBean(beanName, beanDefinition, annotationType, annotationChain); - } - } - - private void processMessagingAnnotationOnBean(String beanName, AnnotatedBeanDefinition beanDefinition, - Class annotationType, List annotationChain) { - - MethodAnnotationPostProcessor messagingAnnotationProcessor = this.postProcessors.get(annotationType); - if (messagingAnnotationProcessor != null) { - if (messagingAnnotationProcessor.beanAnnotationAware()) { - if (messagingAnnotationProcessor.shouldCreateEndpoint( - Objects.requireNonNull(beanDefinition.getFactoryMethodMetadata()).getAnnotations(), annotationChain)) { - messagingAnnotationProcessor.processBeanDefinition(beanName, beanDefinition, annotationChain); - } - else { - throw new BeanDefinitionValidationException( - "The input channel for endpoint on '@Bean' method must be set for the " - + annotationType + ". The bean definition with the problem is: " + beanName); - } - } - else { - throw new BeanDefinitionValidationException( - "The messaging annotation '" + annotationType + "' cannot be declared on '@Bean'. " + - "The bean definition with the problem is: " + beanName); - } - } - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - protected ConfigurableListableBeanFactory getBeanFactory() { - return this.beanFactory; - } - - protected BeanDefinitionRegistry getBeanDefinitionRegistry() { - return this.registry; - } - - protected @Nullable Map, MethodAnnotationPostProcessor> setupCustomPostProcessors() { - return null; - } - - public void addMessagingAnnotationPostProcessor(Class annotation, - MethodAnnotationPostProcessor postProcessor) { - - this.postProcessors.put(annotation, postProcessor); - } - - protected Map, MethodAnnotationPostProcessor> getPostProcessors() { - return this.postProcessors; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java deleted file mode 100644 index 657cc7d72dc..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2023-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.time.Duration; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * The {@link FactoryBean} to produce a {@link PeriodicTrigger} - * based on parsing string values for its options. - * This class is mostly driven by the XML configuration requirements for - * {@link Duration} value representations for the respective attributes. - * - * @author Artem Bilan - * - * @since 6.2 - */ -public class PeriodicTriggerFactoryBean implements FactoryBean { - - @Nullable - private String fixedDelayValue; - - @Nullable - private String fixedRateValue; - - @Nullable - private String initialDelayValue; - - @Nullable - private TimeUnit timeUnit; - - public void setFixedDelayValue(@Nullable String fixedDelayValue) { - this.fixedDelayValue = fixedDelayValue; - } - - public void setFixedRateValue(@Nullable String fixedRateValue) { - this.fixedRateValue = fixedRateValue; - } - - public void setInitialDelayValue(String initialDelayValue) { - this.initialDelayValue = initialDelayValue; - } - - public void setTimeUnit(TimeUnit timeUnit) { - this.timeUnit = timeUnit; - } - - @Override - public PeriodicTrigger getObject() { - boolean hasFixedDelay = StringUtils.hasText(this.fixedDelayValue); - boolean hasFixedRate = StringUtils.hasText(this.fixedRateValue); - - Assert.isTrue(hasFixedDelay ^ hasFixedRate, - "One of the 'fixedDelayValue' or 'fixedRateValue' property must be provided but not both."); - - TimeUnit timeUnitToUse = this.timeUnit; - if (timeUnitToUse == null) { - timeUnitToUse = TimeUnit.MILLISECONDS; - } - - String value = hasFixedDelay ? this.fixedDelayValue : this.fixedRateValue; - Duration duration = toDuration(Objects.requireNonNull(value), timeUnitToUse); - - PeriodicTrigger periodicTrigger = new PeriodicTrigger(duration); - periodicTrigger.setFixedRate(hasFixedRate); - if (StringUtils.hasText(this.initialDelayValue)) { - periodicTrigger.setInitialDelay(toDuration(this.initialDelayValue, timeUnitToUse)); - } - return periodicTrigger; - } - - @Override - public Class getObjectType() { - return PeriodicTrigger.class; - } - - private static Duration toDuration(String value, TimeUnit timeUnit) { - if (isDurationString(value)) { - return Duration.parse(value); - } - return toDuration(Long.parseLong(value), timeUnit); - } - - private static boolean isDurationString(String value) { - return (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))); - } - - private static boolean isP(char ch) { - return (ch == 'P' || ch == 'p'); - } - - private static Duration toDuration(long value, TimeUnit timeUnit) { - return Duration.of(value, timeUnit.toChronoUnit()); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/PublisherRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/PublisherRegistrar.java deleted file mode 100644 index 210abe5915e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/PublisherRegistrar.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.util.StringUtils; - -/** - * @author Artem Bilan - * @author Gary Russell - * @author Chris Bono - * - * @since 4.0 - */ -public class PublisherRegistrar implements ImportBeanDefinitionRegistrar { - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME)) { - throw new BeanDefinitionStoreException("Only one enable publisher definition " + - "(@EnablePublisher or ) can be declared in the application context."); - } - Map annotationAttributes = - importingClassMetadata.getAnnotationAttributes(EnablePublisher.class.getName()); - - String defaultChannel = - annotationAttributes == null - ? (String) AnnotationUtils.getDefaultValue(EnablePublisher.class) - : (String) annotationAttributes.get("defaultChannel"); - - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(PublisherAnnotationBeanPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - - if (StringUtils.hasText(defaultChannel)) { - builder.addPropertyValue("defaultChannelName", defaultChannel); - } - - if (annotationAttributes != null) { - builder.addPropertyValue("proxyTargetClass", annotationAttributes.get("proxyTargetClass")) - .addPropertyValue("order", annotationAttributes.get("order")); - } - registry.registerBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME, - builder.getBeanDefinition()); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ReleaseStrategyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ReleaseStrategyFactoryBean.java deleted file mode 100644 index c7c13489378..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ReleaseStrategyFactoryBean.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Method; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.integration.aggregator.MethodInvokingReleaseStrategy; -import org.springframework.integration.aggregator.ReleaseStrategy; -import org.springframework.integration.aggregator.SimpleSequenceSizeReleaseStrategy; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.util.StringUtils; - -/** - * Convenience factory for XML configuration of a {@link ReleaseStrategy}. - * Encapsulates the knowledge of the default strategy and search algorithms for POJO and annotated methods. - * - * @author Dave Syer - * @author Gary Russell - * @author Artem Bilan - * - */ -public class ReleaseStrategyFactoryBean implements FactoryBean, InitializingBean { - - private static final Log LOGGER = LogFactory.getLog(ReleaseStrategyFactoryBean.class); - - private @Nullable Object target; - - private @Nullable String methodName; - - private ReleaseStrategy strategy = new SimpleSequenceSizeReleaseStrategy(); - - public void setTarget(Object target) { - this.target = target; - } - - public void setMethodName(String methodName) { - this.methodName = methodName; - } - - @Override - public void afterPropertiesSet() { - if (this.target instanceof ReleaseStrategy && !StringUtils.hasText(this.methodName)) { - this.strategy = (ReleaseStrategy) this.target; - return; - } - if (this.target != null) { - if (StringUtils.hasText(this.methodName)) { - this.strategy = new MethodInvokingReleaseStrategy(this.target, this.methodName); - } - else { - Method method = MessagingAnnotationUtils.findAnnotatedMethod(this.target, - org.springframework.integration.annotation.ReleaseStrategy.class); - if (method != null) { - this.strategy = new MethodInvokingReleaseStrategy(this.target, method); - } - else { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("No ReleaseStrategy annotated method found on " - + this.target.getClass().getSimpleName() - + "; falling back to SimpleSequenceSizeReleaseStrategy, target: " - + this.target + ", methodName: " + this.methodName); - } - } - } - } - else { - LOGGER.warn("No target supplied; falling back to SimpleSequenceSizeReleaseStrategy"); - } - } - - @NonNull - @Override - public ReleaseStrategy getObject() { - return this.strategy; - } - - @Override - public Class getObjectType() { - return ReleaseStrategy.class; - } - - @Override - public boolean isSingleton() { - return true; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterAnnotationPostProcessor.java deleted file mode 100644 index bff94817c9b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterAnnotationPostProcessor.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.stream.Collectors; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.ResolvableType; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.integration.annotation.Router; -import org.springframework.integration.router.AbstractMessageRouter; -import org.springframework.integration.router.MethodInvokingRouter; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -/** - * Post-processor for Methods annotated with {@link Router @Router}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - private static final String APPLY_SEQUENCE_ATTR = "applySequence"; - - private static final String IGNORE_SEND_FAILURES_ATTR = "ignoreSendFailures"; - - private static final String CHANNEL_MAPPINGS_ATTR = "channelMappings"; - - private static final String RESOLUTION_REQUIRED_ATTR = "resolutionRequired"; - - private static final String PREFIX_ATTR = "prefix"; - - private static final String SUFFIX_ATTR = "suffix"; - - public RouterAnnotationPostProcessor() { - this.messageHandlerAttributes.addAll(Arrays.asList("defaultOutputChannel", APPLY_SEQUENCE_ATTR, - IGNORE_SEND_FAILURES_ATTR, RESOLUTION_REQUIRED_ATTR, CHANNEL_MAPPINGS_ATTR, PREFIX_ATTR, SUFFIX_ATTR)); - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotations) { - - BeanDefinition handlerBeanDefinition = - super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); - - if (handlerBeanDefinition != null) { - return handlerBeanDefinition; - } - - BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); - if (targetObjectBeanDefinition == null) { - targetObjectBeanDefinition = new RuntimeBeanReference(beanName); - } - - BeanDefinition routerBeanDefinition = - BeanDefinitionBuilder.genericBeanDefinition(RouterFactoryBean.class) - .addPropertyValue("targetObject", targetObjectBeanDefinition) - .getBeanDefinition(); - - new BeanDefinitionPropertiesMapper(routerBeanDefinition, annotations) - .setPropertyValue(APPLY_SEQUENCE_ATTR) - .setPropertyValue(IGNORE_SEND_FAILURES_ATTR) - .setPropertyValue(RESOLUTION_REQUIRED_ATTR) - .setPropertyValue(PREFIX_ATTR) - .setPropertyValue(SUFFIX_ATTR); - - String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, CHANNEL_MAPPINGS_ATTR, - String[].class); - if (!ObjectUtils.isEmpty(channelMappings)) { - Map mappings = - Arrays.stream(channelMappings) - .map((mapping) -> { - String[] keyValue = mapping.split("="); - return Map.entry(keyValue[0], keyValue[1]); - }) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - routerBeanDefinition.getPropertyValues() - .addPropertyValue(CHANNEL_MAPPINGS_ATTR, mappings); - } - - return routerBeanDefinition; - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - AbstractMessageRouter router = new MethodInvokingRouter(bean, method); - String defaultOutputChannelName = - MessagingAnnotationUtils.resolveAttribute(annotations, "defaultOutputChannel", String.class); - if (StringUtils.hasText(defaultOutputChannelName)) { - router.setDefaultOutputChannelName(defaultOutputChannelName); - } - - String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, APPLY_SEQUENCE_ATTR, String.class); - if (StringUtils.hasText(applySequence)) { - router.setApplySequence(resolveAttributeToBoolean(applySequence)); - } - - String ignoreSendFailures = MessagingAnnotationUtils.resolveAttribute(annotations, IGNORE_SEND_FAILURES_ATTR, - String.class); - if (StringUtils.hasText(ignoreSendFailures)) { - router.setIgnoreSendFailures(resolveAttributeToBoolean(ignoreSendFailures)); - } - - routerAttributes(annotations, router); - - return router; - } - - private void routerAttributes(List annotations, AbstractMessageRouter router) { - if (routerAttributesProvided(annotations)) { - - MethodInvokingRouter methodInvokingRouter = (MethodInvokingRouter) router; - - String resolutionRequired = MessagingAnnotationUtils.resolveAttribute(annotations, RESOLUTION_REQUIRED_ATTR, - String.class); - if (StringUtils.hasText(resolutionRequired)) { - methodInvokingRouter.setResolutionRequired(resolveAttributeToBoolean(resolutionRequired)); - } - - ConfigurableListableBeanFactory beanFactory = getBeanFactory(); - - String prefix = MessagingAnnotationUtils.resolveAttribute(annotations, PREFIX_ATTR, String.class); - if (StringUtils.hasText(prefix)) { - methodInvokingRouter.setPrefix(beanFactory.resolveEmbeddedValue(prefix)); - } - - String suffix = MessagingAnnotationUtils.resolveAttribute(annotations, SUFFIX_ATTR, String.class); - if (StringUtils.hasText(suffix)) { - methodInvokingRouter.setSuffix(beanFactory.resolveEmbeddedValue(suffix)); - } - - String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, CHANNEL_MAPPINGS_ATTR, - String[].class); - if (!ObjectUtils.isEmpty(channelMappings)) { - StringBuilder mappings = new StringBuilder(); - for (String channelMapping : channelMappings) { - mappings.append(channelMapping).append("\n"); - } - Properties properties = (Properties) getConversionService().convert(mappings.toString(), - TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Properties.class)); - methodInvokingRouter.replaceChannelMappings(properties); - } - - } - } - - private boolean routerAttributesProvided(List annotations) { - String defaultOutputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, "defaultOutputChannel", - String.class); - String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, CHANNEL_MAPPINGS_ATTR, - String[].class); - String prefix = MessagingAnnotationUtils.resolveAttribute(annotations, PREFIX_ATTR, String.class); - String suffix = MessagingAnnotationUtils.resolveAttribute(annotations, SUFFIX_ATTR, String.class); - String resolutionRequired = MessagingAnnotationUtils.resolveAttribute(annotations, RESOLUTION_REQUIRED_ATTR, - String.class); - String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, APPLY_SEQUENCE_ATTR, String.class); - String ignoreSendFailures = MessagingAnnotationUtils.resolveAttribute(annotations, IGNORE_SEND_FAILURES_ATTR, - String.class); - return StringUtils.hasText(defaultOutputChannel) || !ObjectUtils.isEmpty(channelMappings) // NOSONAR complexity - || StringUtils.hasText(prefix) || StringUtils.hasText(suffix) || StringUtils.hasText(resolutionRequired) - || StringUtils.hasText(applySequence) || StringUtils.hasText(ignoreSendFailures); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java deleted file mode 100644 index a653e306f0b..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.JavaUtils; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.router.AbstractMappingMessageRouter; -import org.springframework.integration.router.AbstractMessageRouter; -import org.springframework.integration.router.ExpressionEvaluatingRouter; -import org.springframework.integration.router.MethodInvokingRouter; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Factory bean for creating a Message Router. - * - * @author Mark Fisher - * @author Jonas Partner - * @author Oleg Zhurakousky - * @author Dave Syer - * @author Gary Russell - * @author David Liu - * @author Artem Bilan - */ -public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean { - - @Nullable - private Map channelMappings; - - @Nullable - private MessageChannel defaultOutputChannel; - - @Nullable - private String defaultOutputChannelName; - - @Nullable - private String prefix; - - @Nullable - private String suffix; - - @Nullable - private Boolean resolutionRequired; - - @Nullable - private Boolean applySequence; - - @Nullable - private Boolean ignoreSendFailures; - - public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) { - this.defaultOutputChannel = defaultOutputChannel; - } - - public void setDefaultOutputChannelName(String defaultOutputChannelName) { - this.defaultOutputChannelName = defaultOutputChannelName; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public void setSuffix(String suffix) { - this.suffix = suffix; - } - - public void setResolutionRequired(Boolean resolutionRequired) { - this.resolutionRequired = resolutionRequired; - } - - public void setApplySequence(Boolean applySequence) { - this.applySequence = applySequence; - } - - public void setIgnoreSendFailures(Boolean ignoreSendFailures) { - this.ignoreSendFailures = ignoreSendFailures; - } - - public void setChannelMappings(Map channelMappings) { - this.channelMappings = channelMappings; - } - - @Override - protected MessageHandler createMethodInvokingHandler(Object targetObject, @Nullable String targetMethodName) { - Assert.notNull(targetObject, "target object must not be null"); - AbstractMessageRouter router = - IntegrationObjectSupport.extractTypeIfPossible(targetObject, AbstractMessageRouter.class); - if (router == null) { - if (targetObject instanceof MessageHandler && this.noRouterAttributesProvided() - && this.methodIsHandleMessageOrEmpty(targetMethodName)) { - return (MessageHandler) targetObject; - } - router = createMethodInvokingRouter(targetObject, targetMethodName); - configureRouter(router); - } - else { - Assert.isTrue(!StringUtils.hasText(targetMethodName), - "target method should not be provided when the target " - + "object is an implementation of AbstractMessageRouter"); - configureRouter(router); - if (targetObject instanceof MessageHandler) { - return (MessageHandler) targetObject; - } - } - return router; - } - - @Override - protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - return configureRouter(new ExpressionEvaluatingRouter(expression)); - } - - protected AbstractMappingMessageRouter createMethodInvokingRouter(Object targetObject, @Nullable String targetMethodName) { - return (StringUtils.hasText(targetMethodName)) - ? new MethodInvokingRouter(targetObject, targetMethodName) - : new MethodInvokingRouter(targetObject); - } - - protected AbstractMessageRouter configureRouter(AbstractMessageRouter router) { - JavaUtils.INSTANCE - .acceptIfNotNull(this.defaultOutputChannel, router::setDefaultOutputChannel) - .acceptIfNotNull(this.defaultOutputChannelName, router::setDefaultOutputChannelName) - .acceptIfNotNull(getSendTimeout(), router::setSendTimeout) - .acceptIfNotNull(this.applySequence, router::setApplySequence) - .acceptIfNotNull(this.ignoreSendFailures, router::setIgnoreSendFailures); - - if (router instanceof AbstractMappingMessageRouter) { - configureMappingRouter((AbstractMappingMessageRouter) router); - } - return router; - } - - protected void configureMappingRouter(AbstractMappingMessageRouter router) { - JavaUtils.INSTANCE - .acceptIfNotNull(this.channelMappings, router::setChannelMappings) - .acceptIfNotNull(this.resolutionRequired, router::setResolutionRequired) - .acceptIfHasText(this.prefix, router::setPrefix) - .acceptIfHasText(this.suffix, router::setSuffix); - } - - @Override - protected boolean canBeUsedDirect(AbstractMessageProducingHandler handler) { - return noRouterAttributesProvided(); - } - - protected boolean noRouterAttributesProvided() { - return this.channelMappings == null && this.defaultOutputChannel == null // NOSONAR boolean complexity - && getSendTimeout() == null && this.resolutionRequired == null && this.applySequence == null - && this.ignoreSendFailures == null; - } - - @Override - protected Class getPreCreationHandlerType() { - return AbstractMessageRouter.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorAnnotationPostProcessor.java deleted file mode 100644 index 365d692f923..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorAnnotationPostProcessor.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.ResolvableType; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.ServiceActivatingHandler; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.StringUtils; - -/** - * Post-processor for Methods annotated with {@link ServiceActivator @ServiceActivator}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Yilin Wei - */ -public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - public ServiceActivatorAnnotationPostProcessor() { - this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "requiresReply")); - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotations) { - - BeanDefinition handlerBeanDefinition = - super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); - - if (handlerBeanDefinition != null) { - return handlerBeanDefinition; - } - - BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); - if (targetObjectBeanDefinition == null) { - targetObjectBeanDefinition = new RuntimeBeanReference(beanName); - } - - BeanDefinition serviceActivatorBeanDefinition = - BeanDefinitionBuilder.genericBeanDefinition(ServiceActivatorFactoryBean.class) - .addPropertyValue("targetObject", targetObjectBeanDefinition) - .getBeanDefinition(); - - new BeanDefinitionPropertiesMapper(serviceActivatorBeanDefinition, annotations) - .setPropertyValue("requiresReply") - .setPropertyValue("async"); - - return serviceActivatorBeanDefinition; - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - AbstractReplyProducingMessageHandler serviceActivator = new ServiceActivatingHandler(bean, method); - - String requiresReply = MessagingAnnotationUtils.resolveAttribute(annotations, "requiresReply", String.class); - if (StringUtils.hasText(requiresReply)) { - serviceActivator.setRequiresReply(resolveAttributeToBoolean(requiresReply)); - } - - String isAsync = MessagingAnnotationUtils.resolveAttribute(annotations, "async", String.class); - if (StringUtils.hasText(isAsync)) { - serviceActivator.setAsync(resolveAttributeToBoolean(isAsync)); - } - - setOutputChannelIfPresent(annotations, serviceActivator); - return serviceActivator; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java deleted file mode 100644 index 5f4501b2fb3..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.Arrays; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; -import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper; -import org.springframework.integration.handler.ServiceActivatingHandler; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.ReactiveMessageHandler; -import org.springframework.util.StringUtils; - -/** - * FactoryBean for creating {@link ServiceActivatingHandler} instances. - * - * @author Mark Fisher - * @author Gary Russell - * @author David Liu - * @author Artem Bilan - * - * @since 2.0 - */ -public class ServiceActivatorFactoryBean extends AbstractStandardMessageHandlerFactoryBean { - - @SuppressWarnings("NullAway.Init") - private String[] headers; - - public void setNotPropagatedHeaders(String... headers) { - this.headers = Arrays.copyOf(headers, headers.length); - } - - @Override - protected MessageHandler createMethodInvokingHandler(Object targetObject, @Nullable String targetMethodName) { - MessageHandler handler; - handler = createDirectHandlerIfPossible(targetObject, targetMethodName); - if (handler == null) { - handler = configureHandler( - StringUtils.hasText(targetMethodName) - ? new ServiceActivatingHandler(targetObject, targetMethodName) - : new ServiceActivatingHandler(targetObject)); - } - return handler; - } - - /** - * If the target object is a {@link MessageHandler} and the method is 'handleMessage', return an - * {@link AbstractMessageProducingHandler} that wraps it. - * @param targetObject the object to check for Direct Handler requirements. - * @param targetMethodName the method name to check for Direct Handler requirements. - * @return the {@code targetObject} as a Direct {@link MessageHandler} or {@code null}. - */ - protected @Nullable MessageHandler createDirectHandlerIfPossible(final Object targetObject, @Nullable String targetMethodName) { - MessageHandler handler = null; - if ((targetObject instanceof MessageHandler || targetObject instanceof ReactiveMessageHandler) - && methodIsHandleMessageOrEmpty(targetMethodName)) { - if (targetObject instanceof AbstractMessageProducingHandler) { - // should never happen but just return it if it's already an AMPH - return (MessageHandler) targetObject; - } - if (targetObject instanceof ReactiveMessageHandler) { - handler = new ReactiveMessageHandlerAdapter((ReactiveMessageHandler) targetObject); - } - else { - /* - * Return a reply-producing message handler so that we still get 'produced no reply' messages - * and the super class will inject the advice chain to advise the handler method if needed. - */ - handler = new ReplyProducingMessageHandlerWrapper((MessageHandler) targetObject); - } - } - return handler; - } - - @Override - protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor<>(expression); - processor.setBeanFactory(getBeanFactory()); - ServiceActivatingHandler handler = new ServiceActivatingHandler(processor); - handler.setPrimaryExpression(expression); - return this.configureHandler(handler); - } - - @Override - protected MessageHandler createMessageProcessingHandler(MessageProcessor processor) { - return configureHandler(new ServiceActivatingHandler(processor)); - } - - protected MessageHandler configureHandler(ServiceActivatingHandler handler) { - postProcessReplyProducer(handler); - return handler; - } - - /** - * Always returns true - any {@link AbstractMessageProducingHandler} can - * be used directly. - */ - @Override - protected boolean canBeUsedDirect(AbstractMessageProducingHandler handler) { - return true; - } - - @Override - protected void postProcessReplyProducer(AbstractMessageProducingHandler handler) { - super.postProcessReplyProducer(handler); - if (this.headers != null) { - handler.setNotPropagatedHeaders(this.headers); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java deleted file mode 100644 index 582bdcd968d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.SmartLifecycle; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.scheduling.PollerMetadata; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.core.BeanFactoryMessageChannelDestinationResolver; -import org.springframework.messaging.core.DestinationResolver; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * FactoryBean for creating a SourcePollingChannelAdapter instance. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Christian Tzolov - */ -public class SourcePollingChannelAdapterFactoryBean implements FactoryBean, - BeanFactoryAware, BeanNameAware, BeanClassLoaderAware, InitializingBean, SmartLifecycle, DisposableBean { - - private final Lock initializationMonitor = new ReentrantLock(); - - @SuppressWarnings("NullAway.Init") - private MessageSource source; - - @Nullable - private MessageChannel outputChannel; - - @Nullable - private String outputChannelName; - - @Nullable - private PollerMetadata pollerMetadata; - - @Nullable - private Boolean autoStartup; - - private int phase = Integer.MAX_VALUE / 2; - - @Nullable - private Long sendTimeout; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - @SuppressWarnings("NullAway.Init") - private ConfigurableBeanFactory beanFactory; - - @SuppressWarnings("NullAway.Init") - private ClassLoader beanClassLoader; - - @SuppressWarnings("NullAway.Init") - private DestinationResolver channelResolver; - - @Nullable - private String role; - - @Nullable - private TaskScheduler taskScheduler; - - @SuppressWarnings("NullAway.Init") - private volatile SourcePollingChannelAdapter adapter; - - private volatile boolean initialized; - - public void setSource(MessageSource source) { - this.source = source; - } - - public void setSendTimeout(long sendTimeout) { - this.sendTimeout = sendTimeout; - } - - public void setOutputChannel(MessageChannel outputChannel) { - this.outputChannel = outputChannel; - } - - public void setOutputChannelName(String outputChannelName) { - this.outputChannelName = outputChannelName; - } - - public void setPollerMetadata(PollerMetadata pollerMetadata) { - this.pollerMetadata = pollerMetadata; - } - - public void setAutoStartup(Boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public void setPhase(int phase) { - this.phase = phase; - } - - public void setRole(String role) { - this.role = role; - } - - /** - * Set a {@link TaskScheduler} for polling tasks. - * @param taskScheduler the {@link TaskScheduler} for polling tasks. - * @since 6.4 - */ - public void setTaskScheduler(TaskScheduler taskScheduler) { - this.taskScheduler = taskScheduler; - } - - /** - * Specify the {@link DestinationResolver} strategy to use. - * The default is a BeanFactoryChannelResolver. - * @param channelResolver The channel resolver. - * @since 4.1.3 - */ - public void setChannelResolver(DestinationResolver channelResolver) { - Assert.notNull(channelResolver, "'channelResolver' must not be null"); - this.channelResolver = channelResolver; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory, - "a ConfigurableBeanFactory is required"); - this.beanFactory = (ConfigurableBeanFactory) beanFactory; - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } - - @Override - public void setBeanName(String beanName) { - this.beanName = beanName; - } - - @Override - public void afterPropertiesSet() { - if (this.channelResolver == null) { - this.channelResolver = new BeanFactoryMessageChannelDestinationResolver(this.beanFactory); - } - initializeAdapter(); - } - - @Override - public SourcePollingChannelAdapter getObject() { - if (this.adapter == null) { - initializeAdapter(); - } - return this.adapter; - } - - @Override - public Class getObjectType() { - return SourcePollingChannelAdapter.class; - } - - private void initializeAdapter() { - this.initializationMonitor.lock(); - try { - if (this.initialized) { - return; - } - Assert.notNull(this.source, "source is required"); - - SourcePollingChannelAdapter spca = new SourcePollingChannelAdapter(); - spca.setSource(this.source); - - if (StringUtils.hasText(this.outputChannelName)) { - Assert.isNull(this.outputChannel, "'outputChannelName' and 'outputChannel' are mutually exclusive."); - spca.setOutputChannelName(this.outputChannelName); - } - else { - Assert.notNull(this.outputChannel, "outputChannel is required"); - spca.setOutputChannel(this.outputChannel); - } - - if (this.pollerMetadata == null) { - this.pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory); - Assert.notNull(this.pollerMetadata, () -> "No poller has been defined for channel-adapter '" - + this.beanName + "', and no default poller is available within the context."); - } - long maxMessagesPerPoll = this.pollerMetadata.getMaxMessagesPerPoll(); - if (maxMessagesPerPoll == PollerMetadata.MAX_MESSAGES_UNBOUNDED) { - // the default is 1 since a source might return - // a non-null and non-interruptible value every time it is invoked - maxMessagesPerPoll = 1; - } - spca.setMaxMessagesPerPoll(maxMessagesPerPoll); - if (this.sendTimeout != null) { - spca.setSendTimeout(this.sendTimeout); - } - spca.setTaskExecutor(this.pollerMetadata.getTaskExecutor()); - spca.setAdviceChain(this.pollerMetadata.getAdviceChain()); - spca.setTrigger(this.pollerMetadata.getTrigger()); - spca.setErrorHandler(this.pollerMetadata.getErrorHandler()); - spca.setBeanClassLoader(this.beanClassLoader); - if (this.autoStartup != null) { - spca.setAutoStartup(this.autoStartup); - } - spca.setPhase(this.phase); - spca.setRole(this.role); - spca.setBeanName(this.beanName); - spca.setBeanFactory(this.beanFactory); - spca.setTransactionSynchronizationFactory(this.pollerMetadata.getTransactionSynchronizationFactory()); - if (this.taskScheduler != null) { - spca.setTaskScheduler(this.taskScheduler); - } - spca.afterPropertiesSet(); - this.adapter = spca; - this.initialized = true; - } - finally { - this.initializationMonitor.unlock(); - } - } - - /* - * SmartLifecycle implementation (delegates to the created adapter) - */ - - @Override - public boolean isAutoStartup() { - return (this.adapter == null) || this.adapter.isAutoStartup(); - } - - @Override - public int getPhase() { - return (this.adapter != null) ? this.adapter.getPhase() : 0; - } - - @Override - public boolean isRunning() { - return (this.adapter != null) && this.adapter.isRunning(); - } - - @Override - public void start() { - if (this.adapter != null) { - this.adapter.start(); - } - } - - @Override - public void stop() { - if (this.adapter != null) { - this.adapter.stop(); - } - } - - @Override - public void stop(Runnable callback) { - if (this.adapter != null) { - this.adapter.stop(callback); - } - else { - callback.run(); - } - } - - @Override - public void destroy() { - if (this.adapter != null) { - this.adapter.destroy(); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionFactoryBean.java deleted file mode 100644 index a1ffa89168f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionFactoryBean.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; - -/** - * A {@link FactoryBean} implementation to encapsulate the population of a static {@link Method} - * from the provided {@linkplain #functionClass} and {@linkplain #functionMethodSignature} as - * a valid {@link org.springframework.expression.spel.support.StandardEvaluationContext} function. - * - * @author Artem Bilan - * @since 3.0 - * - * @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction - * @see BeanUtils#resolveSignature - */ -public class SpelFunctionFactoryBean implements FactoryBean, InitializingBean, BeanNameAware { - - private final Class functionClass; - - private final String functionMethodSignature; - - @SuppressWarnings("NullAway.Init") - private String functionName; - - @SuppressWarnings("NullAway.Init") - private Method method; - - public SpelFunctionFactoryBean(Class functionClass, String functionMethodSignature) { - this.functionClass = functionClass; - this.functionMethodSignature = functionMethodSignature; - } - - @Override - public void setBeanName(String name) { - this.functionName = name; - } - - public String getFunctionName() { - return this.functionName; - } - - @Override - public void afterPropertiesSet() { - Method method = BeanUtils.resolveSignature(this.functionMethodSignature, this.functionClass); - - if (method == null) { - throw new BeanDefinitionStoreException(String.format("No declared method '%s' in class '%s'", - this.functionMethodSignature, this.functionClass)); - } - this.method = method; - if (!Modifier.isStatic(this.method.getModifiers())) { - throw new BeanDefinitionStoreException("SpEL-function method has to be 'static'"); - } - } - - @Override - public Method getObject() { - return this.method; - } - - @Override - public Class getObjectType() { - return Method.class; - } - - @Override - public boolean isSingleton() { - return true; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterAnnotationPostProcessor.java deleted file mode 100644 index 50ae5c6878a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterAnnotationPostProcessor.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.ResolvableType; -import org.springframework.integration.annotation.Splitter; -import org.springframework.integration.splitter.AbstractMessageSplitter; -import org.springframework.integration.splitter.MethodInvokingSplitter; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.StringUtils; - -/** - * Post-processor for Methods annotated with {@link Splitter @Splitter}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - private static final String APPLY_SEQUENCE_ATTR = "applySequence"; - - public SplitterAnnotationPostProcessor() { - this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", APPLY_SEQUENCE_ATTR, "adviceChain")); - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotations) { - - BeanDefinition handlerBeanDefinition = - super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); - - if (handlerBeanDefinition != null) { - return handlerBeanDefinition; - } - - BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); - if (targetObjectBeanDefinition == null) { - targetObjectBeanDefinition = new RuntimeBeanReference(beanName); - } - - BeanDefinitionBuilder splitterBeanDefinition = - BeanDefinitionBuilder.genericBeanDefinition(SplitterFactoryBean.class) - .addPropertyValue("targetObject", targetObjectBeanDefinition); - - String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, APPLY_SEQUENCE_ATTR, String.class); - if (StringUtils.hasText(applySequence)) { - splitterBeanDefinition.addPropertyValue(APPLY_SEQUENCE_ATTR, applySequence); - } - return splitterBeanDefinition.getBeanDefinition(); - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - AbstractMessageSplitter splitter = new MethodInvokingSplitter(bean, method); - - String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, APPLY_SEQUENCE_ATTR, String.class); - if (StringUtils.hasText(applySequence)) { - splitter.setApplySequence(resolveAttributeToBoolean(applySequence)); - } - - setOutputChannelIfPresent(annotations, splitter); - return splitter; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java deleted file mode 100644 index 3c4cda728ee..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.splitter.AbstractMessageSplitter; -import org.springframework.integration.splitter.DefaultMessageSplitter; -import org.springframework.integration.splitter.ExpressionEvaluatingSplitter; -import org.springframework.integration.splitter.MethodInvokingSplitter; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Factory bean for creating a Message Splitter. - * - * @author Mark Fisher - * @author Iwein Fuld - * @author Gary Russell - * @author David Liu - * @author Artem Bilan - */ -public class SplitterFactoryBean extends AbstractStandardMessageHandlerFactoryBean { - - @Nullable - private Boolean applySequence; - - @Nullable - private String delimiters; - - @Nullable - private MessageChannel discardChannel; - - @Nullable - private String discardChannelName; - - public void setApplySequence(boolean applySequence) { - this.applySequence = applySequence; - } - - public void setDelimiters(String delimiters) { - this.delimiters = delimiters; - } - - public void setDiscardChannel(MessageChannel discardChannel) { - this.discardChannel = discardChannel; - } - - public void setDiscardChannelName(String discardChannelName) { - this.discardChannelName = discardChannelName; - } - - @Override - protected MessageHandler createMethodInvokingHandler(Object targetObject, @Nullable String targetMethodName) { - Assert.notNull(targetObject, "targetObject must not be null"); - AbstractMessageSplitter splitter = - IntegrationObjectSupport.extractTypeIfPossible(targetObject, AbstractMessageSplitter.class); - if (splitter == null) { - checkForIllegalTarget(targetObject, targetMethodName); - splitter = createMethodInvokingSplitter(targetObject, targetMethodName); - configureSplitter(splitter); - } - else { - Assert.isTrue(!StringUtils.hasText(targetMethodName), - "target method should not be provided when the target " - + "object is an implementation of AbstractMessageSplitter"); - configureSplitter(splitter); - if (targetObject instanceof MessageHandler) { - return (MessageHandler) targetObject; - } - } - return splitter; - } - - protected AbstractMessageSplitter createMethodInvokingSplitter(Object targetObject, @Nullable String targetMethodName) { - return (StringUtils.hasText(targetMethodName)) - ? new MethodInvokingSplitter(targetObject, targetMethodName) - : new MethodInvokingSplitter(targetObject); - } - - @Override - protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - return configureSplitter(new ExpressionEvaluatingSplitter(expression)); - } - - @Override - protected MessageHandler createDefaultHandler() { - return configureSplitter(new DefaultMessageSplitter()); - } - - protected AbstractMessageSplitter configureSplitter(AbstractMessageSplitter splitter) { - postProcessReplyProducer(splitter); - if (this.discardChannel != null) { - splitter.setDiscardChannel(this.discardChannel); - } - else if (StringUtils.hasText(this.discardChannelName)) { - splitter.setDiscardChannelName(this.discardChannelName); - } - return splitter; - } - - @Override - protected boolean canBeUsedDirect(AbstractMessageProducingHandler handler) { - return handler instanceof AbstractMessageSplitter - || (this.applySequence == null && this.delimiters == null); - } - - @Override - protected void postProcessReplyProducer(AbstractMessageProducingHandler handler) { - super.postProcessReplyProducer(handler); - - if (!(handler instanceof AbstractMessageSplitter)) { - Assert.isNull(this.applySequence, "Cannot set applySequence if the referenced bean is " - + "an AbstractReplyProducingMessageHandler, but not an AbstractMessageSplitter"); - Assert.isNull(this.delimiters, "Cannot set delimiters if the referenced bean is not an " - + "an AbstractReplyProducingMessageHandler, but not an AbstractMessageSplitter"); - } - else { - AbstractMessageSplitter splitter = (AbstractMessageSplitter) handler; - if (this.delimiters != null) { - Assert.isInstanceOf(DefaultMessageSplitter.class, splitter, - "The 'delimiters' property is only available for a Splitter definition where no 'ref', " - + "'expression', or inner bean has been provided."); - ((DefaultMessageSplitter) splitter).setDelimiters(this.delimiters); - } - if (this.applySequence != null) { - splitter.setApplySequence(this.applySequence); - } - } - } - - @Override - protected Class getPreCreationHandlerType() { - return AbstractMessageSplitter.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerAnnotationPostProcessor.java deleted file mode 100644 index be05d91554d..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerAnnotationPostProcessor.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.core.ResolvableType; -import org.springframework.integration.transformer.MessageTransformingHandler; -import org.springframework.integration.transformer.MethodInvokingTransformer; -import org.springframework.integration.transformer.Transformer; -import org.springframework.messaging.MessageHandler; - -/** - * Post-processor for Methods annotated with a - * {@link org.springframework.integration.annotation.Transformer @Transformer}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -public class TransformerAnnotationPostProcessor - extends AbstractMethodAnnotationPostProcessor { - - public TransformerAnnotationPostProcessor() { - this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "adviceChain")); - } - - @Override - protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, - ResolvableType handlerBeanType, List annotations) { - - BeanDefinition handlerBeanDefinition = - super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); - - if (handlerBeanDefinition != null) { - return handlerBeanDefinition; - } - - BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); - if (targetObjectBeanDefinition == null) { - targetObjectBeanDefinition = new RuntimeBeanReference(beanName); - } - - return BeanDefinitionBuilder.genericBeanDefinition(TransformerFactoryBean.class) - .addPropertyValue("targetObject", targetObjectBeanDefinition) - .getBeanDefinition(); - } - - @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - Transformer transformer = new MethodInvokingTransformer(bean, method); - MessageTransformingHandler handler = new MessageTransformingHandler(transformer); - setOutputChannelIfPresent(annotations, handler); - return handler; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.java deleted file mode 100644 index ab5c905a52e..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.integration.handler.AbstractMessageProducingHandler; -import org.springframework.integration.transformer.ExpressionEvaluatingTransformer; -import org.springframework.integration.transformer.MessageTransformingHandler; -import org.springframework.integration.transformer.MethodInvokingTransformer; -import org.springframework.integration.transformer.Transformer; -import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Factory bean for creating a Message Transformer. - * - * @author Mark Fisher - * @author Gary Russell - * @author David Liu - * @author Artem Bilan - * @author Ngoc Nhan - */ -public class TransformerFactoryBean extends AbstractStandardMessageHandlerFactoryBean { - - @SuppressWarnings("this-escape") - public TransformerFactoryBean() { - setRequiresReply(true); - } - - @Override - protected MessageHandler createMethodInvokingHandler(Object targetObject, @Nullable String targetMethodName) { - Assert.notNull(targetObject, "targetObject must not be null"); - Transformer transformer = null; - if (targetObject instanceof Transformer castTransformer) { - transformer = castTransformer; - } - else { - this.checkForIllegalTarget(targetObject, targetMethodName); - if (StringUtils.hasText(targetMethodName)) { - transformer = new MethodInvokingTransformer(targetObject, targetMethodName); - } - else { - transformer = new MethodInvokingTransformer(targetObject); - } - } - return this.createHandler(transformer); - } - - @Override - protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - Transformer transformer = new ExpressionEvaluatingTransformer(expression); - MessageTransformingHandler handler = this.createHandler(transformer); - handler.setPrimaryExpression(expression); - return handler; - } - - protected MessageTransformingHandler createHandler(Transformer transformer) { - MessageTransformingHandler handler = new MessageTransformingHandler(transformer); - this.postProcessReplyProducer(handler); - return handler; - } - - /** - * Always returns true - any {@link AbstractMessageProducingHandler} can - * be used directly. - */ - @Override - protected boolean canBeUsedDirect(AbstractMessageProducingHandler handler) { - return true; // Any AMPH can be a transformer - } - - @Override - protected Class getPreCreationHandlerType() { - return MessageTransformingHandler.class; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AnnotationMetadataAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AnnotationMetadataAdapter.java deleted file mode 100644 index 0ca74933d63..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AnnotationMetadataAdapter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.annotation; - -import java.util.Set; - -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.MethodMetadata; - -/** - * An {@link AnnotationMetadata} implementation to expose a metadata - * by the provided {@link java.util.Map} of attributes. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public abstract class AnnotationMetadataAdapter implements AnnotationMetadata { - - private static final RuntimeException UNSUPPORTED_OPERATION = - new UnsupportedOperationException("The class doesn't support this operation"); - - @Override - public Set getAnnotatedMethods(String annotationName) { - throw UNSUPPORTED_OPERATION; - } - - @Override - public MergedAnnotations getAnnotations() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public String getClassName() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public boolean isInterface() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public boolean isAnnotation() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public boolean isAbstract() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public boolean isFinal() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public boolean isIndependent() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public String getEnclosingClassName() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public String getSuperClassName() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public String[] getInterfaceNames() { - throw UNSUPPORTED_OPERATION; - } - - @Override - public String[] getMemberClassNames() { - throw UNSUPPORTED_OPERATION; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.java deleted file mode 100644 index 643411735a6..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.annotation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; - -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.util.StringUtils; - -/** - * Strategy interface for post-processing annotated methods. - * - * @param the target annotation type. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -public interface MethodAnnotationPostProcessor { - - String INPUT_CHANNEL_ATTRIBUTE = "inputChannel"; - - Object postProcess(Object bean, String beanName, Method method, List annotations); - - void processBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, List annotations); - - /** - * Determine if the provided {@code method} and its {@code annotations} are eligible - * to create an {@link org.springframework.integration.endpoint.AbstractEndpoint}. - * @param method the method to check if it is eligible to create an Endpoint - * @param annotations the List of annotations to process - * @return the {@code boolean} flag to determine whether to create an - * {@link org.springframework.integration.endpoint.AbstractEndpoint} - * @since 4.0 - */ - default boolean shouldCreateEndpoint(Method method, List annotations) { - return shouldCreateEndpoint(MergedAnnotations.from(method), annotations); - } - - default boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List annotations) { - String inputChannel = - MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), String.class); - return StringUtils.hasText(inputChannel); - } - - default String getInputChannelAttribute() { - return INPUT_CHANNEL_ATTRIBUTE; - } - - default boolean beanAnnotationAware() { - return true; - } - - default boolean supportsPojoMethod() { - return true; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/package-info.java deleted file mode 100644 index 9ec4b71e05f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting annotation-based configuration. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.config.annotation; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/config/package-info.java deleted file mode 100644 index 107bbde15df..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Base package for configuration. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.config; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelAdapterParser.java deleted file mode 100644 index e3242f9af18..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelAdapterParser.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.MutablePropertyValues; -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.StringUtils; - -/** - * Base parser for Channel Adapters. - *

- * Includes logic to determine {@link org.springframework.messaging.MessageChannel}: - * if 'channel' attribute is defined - uses its value as 'channelName'; - * if 'id' attribute is defined - creates - * {@link org.springframework.integration.channel.DirectChannel} - * at runtime and uses id's value as 'channelName'; - * if current component is defined as nested element inside any other components e.g. <chain> - * 'id' and 'channel' attributes will be ignored and this component will not be parsed as - * {@link org.springframework.integration.endpoint.AbstractEndpoint}. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - */ -public abstract class AbstractChannelAdapterParser extends AbstractBeanDefinitionParser { - - @Override - protected final String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) - throws BeanDefinitionStoreException { - - String id = element.getAttribute(ID_ATTRIBUTE); - if (!element.hasAttribute("channel")) { - // the created channel will get the 'id', so the adapter's bean name includes a suffix - id = id + ".adapter"; - } - else if (!StringUtils.hasText(id)) { - id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry(), - parserContext.isNested()); - } - return id; - } - - @SuppressWarnings("NullAway") - @Override - protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - String channelName = element.getAttribute("channel"); - if (!StringUtils.hasText(channelName)) { - channelName = this.createDirectChannel(element, parserContext); - } - AbstractBeanDefinition beanDefinition = doParse(element, parserContext, channelName); - MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); - if (!parserContext.isNested()) { - String autoStartup = element.getAttribute(IntegrationNamespaceUtils.AUTO_STARTUP); - if (StringUtils.hasText(autoStartup)) { - propertyValues.add("autoStartup", new TypedStringValue(autoStartup)); - } - String phase = element.getAttribute(IntegrationNamespaceUtils.PHASE); - if (StringUtils.hasText(phase)) { - propertyValues.add("phase", new TypedStringValue(phase)); - } - String role = element.getAttribute(IntegrationNamespaceUtils.ROLE); - if (StringUtils.hasText(role)) { - propertyValues.add("role", new TypedStringValue(role)); - } - } - beanDefinition.setResource(parserContext.getReaderContext().getResource()); - beanDefinition.setSource(IntegrationNamespaceUtils.createElementDescription(element)); - return beanDefinition; - } - - private @Nullable String createDirectChannel(Element element, ParserContext parserContext) { - if (parserContext.isNested()) { - return null; - } - return IntegrationNamespaceUtils.createDirectChannel(element, parserContext); - } - - /** - * Subclasses must implement this method to parse the adapter element. - * The name of the MessageChannel bean is provided. - * @param element The element. - * @param parserContext The parser context. - * @param channelName The channel name. - * @return The bean definition. - */ - protected abstract AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelParser.java deleted file mode 100644 index 0c15e1ed2e9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelParser.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.aop.scope.ScopedProxyUtils; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -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.ManagedList; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.channel.FixedSubscriberChannel; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Base class for channel parsers. - * - * @author Mark Fisher - * @author Dave Syer - * @author Artem Bilan - * @author Gary Russell - */ -public abstract class AbstractChannelParser extends AbstractBeanDefinitionParser { - - @SuppressWarnings("rawtypes") - @Override - protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = this.buildBeanDefinition(element, parserContext); - AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); - Element interceptorsElement = DomUtils.getChildElementByTagName(element, "interceptors"); - String datatypeAttr = element.getAttribute("datatype"); - String messageConverter = element.getAttribute("message-converter"); - if (!FixedSubscriberChannel.class.getName().equals(builder.getBeanDefinition().getBeanClassName())) { - ManagedList interceptors = null; - if (interceptorsElement != null) { - ChannelInterceptorParser interceptorParser = new ChannelInterceptorParser(); - interceptors = interceptorParser.parseInterceptors(interceptorsElement, parserContext); - } - if (interceptors == null) { - interceptors = new ManagedList(); - } - if (StringUtils.hasText(datatypeAttr)) { - builder.addPropertyValue("datatypes", datatypeAttr); - } - if (StringUtils.hasText(messageConverter)) { - builder.addPropertyReference("messageConverter", messageConverter); - } - builder.addPropertyValue("interceptors", interceptors); - String scopeAttr = element.getAttribute("scope"); - if (StringUtils.hasText(scopeAttr)) { - builder.setScope(scopeAttr); - } - } - else { - if (interceptorsElement != null) { - parserContext.getReaderContext().error("Cannot have interceptors when 'fixed-subscriber=\"true\"'", element); - } - if (StringUtils.hasText(datatypeAttr)) { - parserContext.getReaderContext().error("Cannot have 'datatype' when 'fixed-subscriber=\"true\"'", element); - } - if (StringUtils.hasText(messageConverter)) { - parserContext.getReaderContext().error("Cannot have 'message-converter' when 'fixed-subscriber=\"true\"'", element); - } - } - beanDefinition.setSource(parserContext.extractSource(element)); - return beanDefinition; - } - - @Override - protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { - String scope = definition.getBeanDefinition().getScope(); - if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(scope) && !AbstractBeanDefinition.SCOPE_SINGLETON.equals(scope) - && !AbstractBeanDefinition.SCOPE_PROTOTYPE.equals(scope)) { - super.registerBeanDefinition(ScopedProxyUtils.createScopedProxy(definition, registry, false), registry); - } - else { - super.registerBeanDefinition(definition, registry); - } - } - - /** - * Subclasses must implement this method to create the bean definition. - * The class must be defined, and any implementation-specific constructor - * arguments or properties should be configured. This base class will - * configure the interceptors including the 'datatype' interceptor if - * the 'datatype' attribute is defined on the channel element. - * - * @param element The element. - * @param parserContext The parser context. - * @return The bean definition builder. - */ - protected abstract BeanDefinitionBuilder buildBeanDefinition(Element element, ParserContext parserContext); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java deleted file mode 100644 index 964dbd4292c..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.Collection; -import java.util.List; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedSet; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.ConsumerEndpointFactoryBean; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Base class parser for elements that create Message Endpoints. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinitionParser { - - protected static final String REF_ATTRIBUTE = "ref"; - - protected static final String METHOD_ATTRIBUTE = "method"; - - protected static final String EXPRESSION_ATTRIBUTE = "expression"; - - @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) - throws BeanDefinitionStoreException { - - String id = element.getAttribute(ID_ATTRIBUTE); - if (!StringUtils.hasText(id)) { - id = element.getAttribute("name"); - } - if (!StringUtils.hasText(id)) { - id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry(), - parserContext.isNested()); - } - return id; - } - - /** - * Parse the MessageHandler. - * - * @param element The element. - * @param parserContext The parser context. - * @return The bean definition builder. - */ - protected abstract BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext); - - protected String getInputChannelAttributeName() { - return "input-channel"; - } - - @Override - protected final @Nullable AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - BeanDefinitionBuilder handlerBuilder = parseHandler(element, parserContext); - IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "output-channel", - "outputChannelName"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "order"); - - Element txElement = DomUtils.getChildElementByTagName(element, "transactional"); - Element adviceChainElement = DomUtils.getChildElementByTagName(element, - IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN); - - @SuppressWarnings("rawtypes") - ManagedList adviceChain = IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, txElement, true, - handlerBuilder.getRawBeanDefinition(), parserContext); - - boolean hasAdviceChain = !CollectionUtils.isEmpty(adviceChain); - if (hasAdviceChain) { - handlerBuilder.addPropertyValue("adviceChain", adviceChain); - } - - AbstractBeanDefinition handlerBeanDefinition = handlerBuilder.getBeanDefinition(); - handlerBeanDefinition.setResource(parserContext.getReaderContext().getResource()); - String elementDescription = IntegrationNamespaceUtils.createElementDescription(element); - handlerBeanDefinition.setSource(elementDescription); - String inputChannelAttributeName = getInputChannelAttributeName(); - boolean hasInputChannelAttribute = element.hasAttribute(inputChannelAttributeName); - if (parserContext.isNested()) { - if (hasInputChannelAttribute) { - parserContext.getReaderContext().error("The '" + inputChannelAttributeName - + "' attribute isn't allowed for a nested (e.g. inside a ) endpoint element: " - + elementDescription + ".", element); - } - if (!replyChannelInChainAllowed(element) - && StringUtils.hasText(element.getAttribute("reply-channel"))) { - parserContext.getReaderContext().error("The 'reply-channel' attribute isn't" - + " allowed for a nested (e.g. inside a ) outbound gateway element: " - + elementDescription + ".", element); - } - return handlerBeanDefinition; - } - else { - if (!hasInputChannelAttribute) { - parserContext.getReaderContext().error("The '" + inputChannelAttributeName - + "' attribute is required for the top-level endpoint element: " - + elementDescription + ".", element); - } - } - - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class); - - if (hasAdviceChain) { - builder.addPropertyValue("adviceChain", adviceChain); - } - - String handlerBeanName = - BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry()); - String[] handlerAlias = IntegrationNamespaceUtils.generateAlias(element); - parserContext.registerBeanComponent( - new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName, handlerAlias)); - - builder.addPropertyReference("handler", handlerBeanName); - - String inputChannelName = element.getAttribute(inputChannelAttributeName); - - if (!parserContext.getRegistry().containsBeanDefinition(inputChannelName)) { - registerChannelForCreation(parserContext, inputChannelName, builder); - } - IntegrationNamespaceUtils.checkAndConfigureFixedSubscriberChannel(element, parserContext, inputChannelName, - handlerBeanName); - - builder.addPropertyValue("inputChannelName", inputChannelName); - List pollerElementList = DomUtils.getChildElementsByTagName(element, "poller"); - poller(element, parserContext, builder, pollerElementList); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.ROLE); - - AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); - String beanName = resolveId(element, beanDefinition, parserContext); - parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName)); - return null; - } - - private void poller(Element element, ParserContext parserContext, BeanDefinitionBuilder builder, - List pollerElementList) { - if (!CollectionUtils.isEmpty(pollerElementList)) { - if (pollerElementList.size() != 1) { - parserContext.getReaderContext().error( - "at most one poller element may be configured for an endpoint", element); - } - IntegrationNamespaceUtils.configurePollerMetadata(pollerElementList.get(0), builder, parserContext); - } - } - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private void registerChannelForCreation(ParserContext parserContext, String inputChannelName, - BeanDefinitionBuilder consumerEndpointBuilder) { - - BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry(); - - if (beanDefinitionRegistry.containsBeanDefinition( - IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME)) { - - BeanDefinition channelRegistry = - beanDefinitionRegistry.getBeanDefinition( - IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); - ConstructorArgumentValues caValues = channelRegistry.getConstructorArgumentValues(); - ValueHolder vh = caValues.getArgumentValue(0, Collection.class); - if (vh == null) { //although it should never happen if it does we can fix it - caValues.addIndexedArgumentValue(0, new ManagedSet()); - } - @SuppressWarnings("unchecked") - Collection channelCandidateNames = - (Collection) caValues.getArgumentValue(0, Collection.class) - .getValue(); - channelCandidateNames.add(inputChannelName); - consumerEndpointBuilder.addDependsOn(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); - } - else { - parserContext.getReaderContext().error("Failed to locate '" + - IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME + "'", - beanDefinitionRegistry); - } - } - - /** - * Override to allow 'reply-channel' within a chain, for components where it - * makes sense (e.g. enricher). Default is false for outbound gateways, else true. - * @param element the element. - * @return true to allow a reply channel attribute within a chain. - * @since 4.3 - */ - protected boolean replyChannelInChainAllowed(Element element) { - String localName = element.getLocalName(); - return localName == null || !localName.contains("outbound-gateway"); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractCorrelatingMessageHandlerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractCorrelatingMessageHandlerParser.java deleted file mode 100644 index 13daeb27e9c..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractCorrelatingMessageHandlerParser.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.xml.DomUtils; - -/** - * Base class for parsers that create an instance of - * {@link org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler}. - * - * @author Oleg Zhurakousky - * @author Stefan Ferstl - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.1 - */ -public abstract class AbstractCorrelatingMessageHandlerParser extends AbstractConsumerEndpointParser { - - private static final String CORRELATION_STRATEGY_REF_ATTRIBUTE = "correlation-strategy"; - - private static final String CORRELATION_STRATEGY_METHOD_ATTRIBUTE = "correlation-strategy-method"; - - private static final String CORRELATION_STRATEGY_EXPRESSION_ATTRIBUTE = "correlation-strategy-expression"; - - private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy"; - - private static final String RELEASE_STRATEGY_REF_ATTRIBUTE = "release-strategy"; - - private static final String RELEASE_STRATEGY_METHOD_ATTRIBUTE = "release-strategy-method"; - - private static final String RELEASE_STRATEGY_EXPRESSION_ATTRIBUTE = "release-strategy-expression"; - - private static final String RELEASE_STRATEGY_PROPERTY = "releaseStrategy"; - - private static final String MESSAGE_STORE_ATTRIBUTE = "message-store"; - - private static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel"; - - private static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout"; - - private static final String SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE = "send-partial-result-on-expiry"; - - private static final String EXPIRE_GROUPS_UPON_TIMEOUT = "expire-groups-upon-timeout"; - - private static final String RELEASE_LOCK = "release-lock-before-send"; - - protected void doParse(BeanDefinitionBuilder builder, Element element, @Nullable BeanMetadataElement processor, - ParserContext parserContext) { - IntegrationNamespaceUtils.injectPropertyWithAdapter(CORRELATION_STRATEGY_REF_ATTRIBUTE, - CORRELATION_STRATEGY_METHOD_ATTRIBUTE, - CORRELATION_STRATEGY_EXPRESSION_ATTRIBUTE, CORRELATION_STRATEGY_PROPERTY, "CorrelationStrategy", - element, builder, processor, parserContext); - IntegrationNamespaceUtils.injectPropertyWithAdapter(RELEASE_STRATEGY_REF_ATTRIBUTE, - RELEASE_STRATEGY_METHOD_ATTRIBUTE, - RELEASE_STRATEGY_EXPRESSION_ATTRIBUTE, RELEASE_STRATEGY_PROPERTY, "ReleaseStrategy", - element, builder, processor, parserContext); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, MESSAGE_STORE_ATTRIBUTE); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "scheduler", "taskScheduler"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "lock-registry"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, DISCARD_CHANNEL_ATTRIBUTE, - "discardChannelName"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_TIMEOUT_ATTRIBUTE); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "empty-group-min-timeout", - "minimumTimeoutForEmptyGroups"); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "pop-sequence"); - - BeanDefinition expressionDef = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("group-timeout", - "group-timeout-expression", parserContext, element, false); - builder.addPropertyValue("groupTimeoutExpression", expressionDef); - - Element txElement = DomUtils.getChildElementByTagName(element, "expire-transactional"); - Element adviceChainElement = DomUtils.getChildElementByTagName(element, "expire-advice-chain"); - - IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(adviceChainElement, txElement, - builder.getRawBeanDefinition(), parserContext, "forceReleaseAdviceChain"); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, EXPIRE_GROUPS_UPON_TIMEOUT); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, RELEASE_LOCK); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expire-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expire-duration", - "expireDurationMillis"); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "group-condition-supplier"); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java deleted file mode 100644 index 3f2f950b7cf..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.expression.DynamicExpression; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Base parser class for endpoints that delegate to a method invoker or - * expression evaluator when handling consumed Messages. These classes - * use a FactoryBean implementation to construct the actual endpoint - * instance. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - */ -abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumerEndpointParser { - - @Override - protected final BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - Object source = parserContext.extractSource(element); - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(this.getFactoryBeanClassName()); - BeanComponentDefinition innerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, - parserContext); - String ref = element.getAttribute(REF_ATTRIBUTE); - String expression = element.getAttribute(EXPRESSION_ATTRIBUTE); - boolean hasRef = StringUtils.hasText(ref); - boolean hasExpression = StringUtils.hasText(expression); - Element scriptElement = DomUtils.getChildElementByTagName(element, "script"); - Element expressionElement = DomUtils.getChildElementByTagName(element, "expression"); - if (innerDefinition != null) { - innerDefinition(element, parserContext, source, builder, innerDefinition, hasRef, hasExpression, - expressionElement); - } - else if (scriptElement != null) { - scriptElement(element, parserContext, source, builder, hasRef, hasExpression, scriptElement, - expressionElement); - } - else if (expressionElement != null) { - expressionElement(element, parserContext, source, builder, hasRef, hasExpression, expressionElement); - } - else if (hasRef && hasExpression) { - parserContext.getReaderContext().error( - "Only one of 'ref' or 'expression' is permitted, not both, on element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - else if (hasRef) { - builder.addPropertyReference("targetObject", ref); - } - else if (hasExpression) { - builder.addPropertyValue("expressionString", expression); - } - else if (!this.hasDefaultOption()) { - parserContext.getReaderContext().error("Exactly one of the 'ref' attribute, 'expression' attribute, " + - "or inner bean () definition is required for element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - methodAttribute(element, parserContext, source, builder, innerDefinition, hasRef, hasExpression, - expressionElement); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - this.postProcess(builder, element, parserContext); - return builder; - } - - private void innerDefinition(Element element, ParserContext parserContext, @Nullable Object source, - BeanDefinitionBuilder builder, BeanComponentDefinition innerDefinition, boolean hasRef, - boolean hasExpression, @Nullable Element expressionElement) { - if (hasRef || hasExpression || expressionElement != null) { - parserContext.getReaderContext().error( - "Neither 'ref' nor 'expression' are permitted when an inner bean () is configured on element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - builder.addPropertyValue("targetObject", innerDefinition); - } - - private void scriptElement(Element element, ParserContext parserContext, @Nullable Object source, - BeanDefinitionBuilder builder, boolean hasRef, boolean hasExpression, Element scriptElement, - @Nullable Element expressionElement) { - if (hasRef || hasExpression || expressionElement != null) { - parserContext.getReaderContext().error( - "Neither 'ref' nor 'expression' are permitted when an inner script element is configured on element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - BeanDefinition scriptBeanDefinition = parserContext.getDelegate().parseCustomElement(scriptElement, builder.getBeanDefinition()); - builder.addPropertyValue("targetObject", scriptBeanDefinition); - } - - private void expressionElement(Element element, ParserContext parserContext, @Nullable Object source, - BeanDefinitionBuilder builder, boolean hasRef, boolean hasExpression, Element expressionElement) { - if (hasRef || hasExpression) { - parserContext.getReaderContext().error( - "Neither 'ref' nor 'expression' are permitted when an inner 'expression' element is configured on element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition( - DynamicExpression.class); - String key = expressionElement.getAttribute("key"); - String expressionSourceReference = expressionElement.getAttribute("source"); - dynamicExpressionBuilder.addConstructorArgValue(key); - dynamicExpressionBuilder.addConstructorArgReference(expressionSourceReference); - builder.addPropertyValue("expression", dynamicExpressionBuilder.getBeanDefinition()); - } - - private void methodAttribute(Element element, ParserContext parserContext, @Nullable Object source, - BeanDefinitionBuilder builder, @Nullable BeanComponentDefinition innerDefinition, boolean hasRef, - boolean hasExpression, @Nullable Element expressionElement) { - String method = element.getAttribute(METHOD_ATTRIBUTE); - if (StringUtils.hasText(method)) { - if (hasExpression || expressionElement != null) { - parserContext.getReaderContext().error( - "A 'method' attribute is not permitted when configuring an 'expression' on element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - if (hasRef || innerDefinition != null) { - builder.addPropertyValue("targetMethodName", method); - } - else { - parserContext.getReaderContext().error("A 'method' attribute is only permitted when either " + - "a 'ref' or inner-bean definition is provided on element " + - IntegrationNamespaceUtils.createElementDescription(element) + ".", source); - } - } - } - - /** - * Subclasses may override this no-op method to provide additional configuration. - * - * @param builder The builder. - * @param element The element. - * @param parserContext The parser context. - */ - void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { - } - - abstract boolean hasDefaultOption(); - - abstract String getFactoryBeanClassName(); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractInboundGatewayParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractInboundGatewayParser.java deleted file mode 100644 index 0a43ba71a0c..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractInboundGatewayParser.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Base class for inbound gateway parsers. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - */ -public abstract class AbstractInboundGatewayParser extends AbstractSimpleBeanDefinitionParser { - - @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) - throws BeanDefinitionStoreException { - - String id = super.resolveId(element, definition, parserContext); - if (!StringUtils.hasText(id)) { - id = element.getAttribute("name"); - } - if (!StringUtils.hasText(id)) { - id = parserContext.getReaderContext().generateBeanName(definition); - } - return id; - } - - @Override - protected boolean isEligibleAttribute(String attributeName) { - return !attributeName.equals("name") && !attributeName.equals("request-channel") // NOSONAR boolean complexity - && !attributeName.equals("error-channel") - && !attributeName.equals("reply-channel") && super.isEligibleAttribute(attributeName); - } - - @Override - protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - super.doParse(element, parserContext, builder); - AbstractBeanDefinition beanDefinition = builder.getRawBeanDefinition(); - beanDefinition.setResource(parserContext.getReaderContext().getResource()); - beanDefinition.setSource(IntegrationNamespaceUtils.createElementDescription(element)); - } - - @Override - protected final void postProcess(BeanDefinitionBuilder builder, Element element) { - String requestChannelRef = element.getAttribute("request-channel"); - Assert.hasText(requestChannelRef, "a 'request-channel' reference is required"); - builder.addPropertyValue("requestChannelName", requestChannelRef); - String replyChannel = element.getAttribute("reply-channel"); - if (StringUtils.hasText(replyChannel)) { - builder.addPropertyValue("replyChannelName", replyChannel); - } - String errorChannel = element.getAttribute("error-channel"); - if (StringUtils.hasText(errorChannel)) { - builder.addPropertyValue("errorChannelName", errorChannel); - } - doPostProcess(builder, element); - } - - /** - * Subclasses may add to the bean definition by overriding this method. - * @param builder The builder. - * @param element The element. - */ - protected void doPostProcess(BeanDefinitionBuilder builder, Element element) { - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java deleted file mode 100644 index 83b7f2c520f..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.concurrent.atomic.AtomicBoolean; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.ManagedSet; -import org.springframework.beans.factory.xml.NamespaceHandlerSupport; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.ChannelInitializer; -import org.springframework.integration.config.IntegrationRegistrar; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.context.IntegrationProperties; - -/** - * Base class for NamespaceHandlers that registers a BeanFactoryPostProcessor - * for configuring default bean definitions. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -public abstract class AbstractIntegrationNamespaceHandler extends NamespaceHandlerSupport { - - private final AtomicBoolean initialized = new AtomicBoolean(); - - @Override - public final @Nullable BeanDefinition parse(Element element, ParserContext parserContext) { - if (!this.initialized.getAndSet(true)) { - BeanDefinitionRegistry registry = parserContext.getRegistry(); - new IntegrationRegistrar().registerBeanDefinitions(null, registry); - registerImplicitChannelCreator(registry); - } - return super.parse(element, parserContext); - } - - /** - * This method will auto-register a ChannelInitializer which could also be overridden by the user - * by simply registering a ChannelInitializer {@code } with its {@code autoCreate} property - * set to false to suppress channel creation. - * It will also register a ChannelInitializer$AutoCreateCandidatesCollector - * which simply collects candidate channel names. - * @param registry The {@link BeanDefinitionRegistry} to register additional {@link BeanDefinition}s. - */ - private static void registerImplicitChannelCreator(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(IntegrationContextUtils.CHANNEL_INITIALIZER_BEAN_NAME)) { - String channelsAutoCreateExpression = - IntegrationProperties.getExpressionFor(IntegrationProperties.CHANNELS_AUTOCREATE); - BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class) - .addPropertyValue("autoCreate", channelsAutoCreateExpression); - BeanDefinitionHolder channelCreatorHolder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), - IntegrationContextUtils.CHANNEL_INITIALIZER_BEAN_NAME); - BeanDefinitionReaderUtils.registerBeanDefinition(channelCreatorHolder, registry); - } - - if (!registry.containsBeanDefinition(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME)) { - BeanDefinitionBuilder channelRegistryBuilder = BeanDefinitionBuilder - .genericBeanDefinition(ChannelInitializer.AutoCreateCandidatesCollector.class); - channelRegistryBuilder.addConstructorArgValue(new ManagedSet()); - channelRegistryBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - BeanDefinitionHolder channelRegistryHolder = - new BeanDefinitionHolder(channelRegistryBuilder.getBeanDefinition(), - IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); - BeanDefinitionReaderUtils.registerBeanDefinition(channelRegistryHolder, registry); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java deleted file mode 100644 index c02d5006260..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.ConsumerEndpointFactoryBean; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Base class for outbound Channel Adapter parsers. - * - * If this component is defined as the top-level element in the Spring application context it will produce - * an {@link org.springframework.integration.endpoint.AbstractEndpoint} depending on the channel type. - * If this component is defined as nested element (e.g., inside of the chain) it will produce - * a {@link org.springframework.messaging.MessageHandler}. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Ngoc Nhan - */ -public abstract class AbstractOutboundChannelAdapterParser extends AbstractChannelAdapterParser { - - @Override - protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) { - if (parserContext.isNested()) { - if (channelName != null) { - String elementDescription = IntegrationNamespaceUtils.createElementDescription(element); - parserContext.getReaderContext().error( - "The 'channel' attribute isn't allowed for " + - elementDescription + - " when it is used as a nested element," + - " e.g. inside a ", element); - } - AbstractBeanDefinition consumerBeanDefinition = this.parseConsumer(element, parserContext); - this.configureRequestHandlerAdviceChain(element, parserContext, consumerBeanDefinition, null); - return consumerBeanDefinition; - } - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class); - Element pollerElement = DomUtils.getChildElementByTagName(element, "poller"); - BeanComponentDefinition handlerBeanComponentDefinition = this.doParseAndRegisterConsumer(element, parserContext); - builder.addPropertyReference("handler", handlerBeanComponentDefinition.getBeanName()); - IntegrationNamespaceUtils.checkAndConfigureFixedSubscriberChannel(element, parserContext, channelName, - handlerBeanComponentDefinition.getBeanName()); - if (pollerElement != null) { - if (!StringUtils.hasText(channelName)) { - parserContext.getReaderContext().error( - "outbound channel adapter with a 'poller' requires a 'channel' to poll", element); - } - IntegrationNamespaceUtils.configurePollerMetadata(pollerElement, builder, parserContext); - } - builder.addPropertyValue("inputChannelName", channelName); - - this.configureRequestHandlerAdviceChain(element, parserContext, handlerBeanComponentDefinition.getBeanDefinition(), builder); - - return builder.getBeanDefinition(); - } - - private void configureRequestHandlerAdviceChain(Element element, ParserContext parserContext, - BeanDefinition handlerBeanDefinition, @Nullable BeanDefinitionBuilder consumerBuilder) { - Element txElement = DomUtils.getChildElementByTagName(element, "transactional"); - Element adviceChainElement = DomUtils.getChildElementByTagName(element, - IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN); - @SuppressWarnings("rawtypes") - ManagedList adviceChain = - IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, txElement, handlerBeanDefinition, - parserContext); - if (!CollectionUtils.isEmpty(adviceChain)) { - /* - * For ARPMH, the advice chain is injected so just the handleRequestMessage method is advised. - * Sometime ARPMHs do double duty as a gateway and a channel adapter. The parser subclass - * can indicate this by overriding isUsingReplyProducer(), or we can try to determine it from - * the bean class. - */ - boolean isReplyProducer = this.isUsingReplyProducer(); - if (!isReplyProducer) { - Class beanClass = null; - if (handlerBeanDefinition instanceof AbstractBeanDefinition abstractBeanDefinition) { - if (abstractBeanDefinition.hasBeanClass()) { - beanClass = abstractBeanDefinition.getBeanClass(); - } - } - isReplyProducer = beanClass != null && AbstractReplyProducingMessageHandler.class.isAssignableFrom(beanClass); - } - - if (isReplyProducer) { - handlerBeanDefinition.getPropertyValues().add("adviceChain", adviceChain); - } - else if (consumerBuilder != null) { - consumerBuilder.addPropertyValue("adviceChain", adviceChain); - } - else { - String elementDescription = IntegrationNamespaceUtils.createElementDescription(element); - parserContext.getReaderContext().error("'request-handler-advice-chain' isn't allowed for " + - elementDescription + - " within a , because its Handler " + - "isn't an AbstractReplyProducingMessageHandler", element); - } - } - } - - /** - * Override this method to control the registration process and return the bean name. - * If parsing a bean definition whose name can be auto-generated, consider using - * {@link #parseConsumer(Element, ParserContext)} instead. - * - * @param element The element. - * @param parserContext The parser context. - * @return The bean component definition. - */ - protected BeanComponentDefinition doParseAndRegisterConsumer(Element element, ParserContext parserContext) { - AbstractBeanDefinition definition = this.parseConsumer(element, parserContext); - if (definition == null) { - parserContext.getReaderContext().error("Consumer parsing must return an AbstractBeanDefinition.", element); - } - String order = element.getAttribute(IntegrationNamespaceUtils.ORDER); - if (StringUtils.hasText(order)) { - definition.getPropertyValues().addPropertyValue(IntegrationNamespaceUtils.ORDER, order); - } - String beanName = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); - String[] handlerAlias = IntegrationNamespaceUtils.generateAlias(element); - BeanComponentDefinition beanComponentDefinition = new BeanComponentDefinition(definition, beanName, handlerAlias); - parserContext.registerBeanComponent(beanComponentDefinition); - return beanComponentDefinition; - } - - /** - * Override this method to return the BeanDefinition for the MessageConsumer. It will - * be registered with a generated name. - * - * @param element The element. - * @param parserContext The parser context. - * @return The bean definition. - */ - protected abstract AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext); - - /** - * Override this to signal that this channel adapter is actually using a AbstractReplyProducingMessageHandler - * while it is not possible for this parser to determine that because, say, a FactoryBean is being used. - * - * @return false, unless overridden. - */ - protected boolean isUsingReplyProducer() { - return false; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundGatewayParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundGatewayParser.java deleted file mode 100644 index a2078fd109a..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundGatewayParser.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.StringUtils; - -/** - * Base class for url-based outbound gateway parsers. - * - * @author Mark Fisher - * @author Artem Bilan - */ -public abstract class AbstractOutboundGatewayParser extends AbstractConsumerEndpointParser { - - protected abstract String getGatewayClassName(Element element); - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(this.getGatewayClassName(element)); - String url = this.parseUrl(element, parserContext); - builder.addConstructorArgValue(url); - String replyChannel = element.getAttribute("reply-channel"); - if (StringUtils.hasText(replyChannel)) { - builder.addPropertyReference("replyChannel", replyChannel); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); - this.postProcessGateway(builder, element, parserContext); - return builder; - } - - protected String parseUrl(Element element, ParserContext parserContext) { - String url = element.getAttribute("url"); - if (!StringUtils.hasText(url)) { - parserContext.getReaderContext().error("The 'url' attribute is required.", element); - } - return url; - } - - /** - * Subclasses may override this method for additional configuration. - * @param builder The builder. - * @param element The element. - * @param parserContext The parser context. - */ - protected void postProcessGateway(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java deleted file mode 100644 index 3cacb9503a7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; -import org.springframework.util.xml.DomUtils; - -/** - * Base parser for inbound Channel Adapters that poll a source. - * - * @author Mark Fisher - * @author Gary Russell - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Ngoc Nhan - */ -public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser { - - @Override - protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) { - BeanMetadataElement source = this.parseSource(element, parserContext); - if (source == null) { - parserContext.getReaderContext().error("failed to parse source", element); - } - BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder - .genericBeanDefinition(SourcePollingChannelAdapterFactoryBean.class); - - String sourceBeanName = null; - - if (source instanceof BeanDefinition beanDefinition) { - String channelAdapterId = this.resolveId(element, adapterBuilder.getRawBeanDefinition(), parserContext); - sourceBeanName = channelAdapterId + ".source"; - parserContext.getRegistry().registerBeanDefinition(sourceBeanName, beanDefinition); - } - else if (source instanceof RuntimeBeanReference runtimeBeanReference) { - sourceBeanName = runtimeBeanReference.getBeanName(); - } - else { - parserContext.getReaderContext().error("Wrong 'source' type: must be 'BeanDefinition' or 'RuntimeBeanReference'", source); - // This exception is meant to signal to NullAway that the error method throws an exception - throw new IllegalStateException("Wrong 'source' type: must be 'BeanDefinition' or 'RuntimeBeanReference'"); - } - - adapterBuilder.addPropertyReference("source", sourceBeanName); - adapterBuilder.addPropertyReference("outputChannel", channelName); - IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "send-timeout"); - Element pollerElement = DomUtils.getChildElementByTagName(element, "poller"); - if (pollerElement != null) { - IntegrationNamespaceUtils.configurePollerMetadata(pollerElement, adapterBuilder, parserContext); - } - return adapterBuilder.getBeanDefinition(); - } - - /** - * Subclasses must implement this method to parse the PollableSource instance - * which the created Channel Adapter will poll. - * - * @param element The element. - * @param parserContext The parser context. - * @return The bean metadata element. - */ - protected abstract BeanMetadataElement parseSource(Element element, ParserContext parserContext); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java deleted file mode 100644 index 441a9a2ab91..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.List; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.RouterFactoryBean; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Base parser for routers. - * - * @author Mark Fisher - * @author Gary Russell - * @author Ngoc Nhan - */ -public abstract class AbstractRouterParser extends AbstractConsumerEndpointParser { - - @Override - protected final BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RouterFactoryBean.class); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "default-output-channel"); - if (StringUtils.hasText(element.getAttribute("timeout")) - && StringUtils.hasText(element.getAttribute("send-timeout"))) { - parserContext.getReaderContext().error("Only one of 'timeout' and 'send-timeout' is allowed", element); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "timeout", "sendTimeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "resolution-required"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-send-failures"); - BeanDefinition targetRouterBeanDefinition = this.parseRouter(element, parserContext); - builder.addPropertyValue("targetObject", targetRouterBeanDefinition); - return builder; - } - - protected final BeanDefinition parseRouter(Element element, ParserContext parserContext) { - BeanDefinition beanDefinition = this.doParseRouter(element, parserContext); - if (beanDefinition != null) { - // check if mapping is provided otherwise returned values will be treated as channel names - List mappingElements = DomUtils.getChildElementsByTagName(element, "mapping"); - if (!CollectionUtils.isEmpty(mappingElements)) { - ManagedMap channelMappings = new ManagedMap<>(); - for (Element mappingElement : mappingElements) { - String key = mappingElement.getAttribute(this.getMappingKeyAttributeName()); - channelMappings.put(key, mappingElement.getAttribute("channel")); - } - beanDefinition.getPropertyValues().add("channelMappings", channelMappings); - } - } - return beanDefinition; - } - - /** - * Returns the name of the attribute that provides a key for the - * channel mappings. This can be overridden by subclasses. - * - * @return The mapping key attribute name. - */ - protected String getMappingKeyAttributeName() { - return "value"; - } - - protected abstract BeanDefinition doParseRouter(Element element, ParserContext parserContext); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractTransformerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractTransformerParser.java deleted file mode 100644 index 595649e27cb..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractTransformerParser.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.transformer.MessageTransformingHandler; - -/** - * @author Mark Fisher - */ -public abstract class AbstractTransformerParser extends AbstractConsumerEndpointParser { - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( - MessageTransformingHandler.class); - BeanDefinitionBuilder transformerBuilder = - BeanDefinitionBuilder.genericBeanDefinition(this.getTransformerClassName()); - this.parseTransformer(element, parserContext, transformerBuilder); - String transformerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName( - transformerBuilder.getBeanDefinition(), parserContext.getRegistry()); - builder.addConstructorArgReference(transformerBeanName); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - return builder; - } - - protected abstract String getTransformerClassName(); - - protected abstract void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java deleted file mode 100644 index 77ba59105c6..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AggregatorParser.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.aggregator.DefaultAggregatingMessageGroupProcessor; -import org.springframework.integration.aggregator.ExpressionEvaluatingMessageGroupProcessor; -import org.springframework.integration.config.AggregatorFactoryBean; -import org.springframework.util.StringUtils; - -/** - * Parser for the aggregator element of the integration namespace. Registers the annotation-driven - * post-processors. - * - * @author Marius Bogoevici - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Dave Syer - * @author Stefan Ferstl - * @author Gary Russell - * @author Artem Bilan - */ -public class AggregatorParser extends AbstractCorrelatingMessageHandlerParser { - - private static final String EXPIRE_GROUPS_UPON_COMPLETION = "expire-groups-upon-completion"; - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanComponentDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, - parserContext); - String ref = element.getAttribute(REF_ATTRIBUTE); - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AggregatorFactoryBean.class); - String headersFunction = element.getAttribute("headers-function"); - BeanMetadataElement processor = null; - - if (innerHandlerDefinition != null || StringUtils.hasText(ref)) { - if (innerHandlerDefinition != null) { - processor = innerHandlerDefinition; - } - else { - processor = new RuntimeBeanReference(ref); - } - builder.addPropertyValue("processorBean", processor); - if (StringUtils.hasText(headersFunction)) { - builder.addPropertyReference("headersFunction", headersFunction); - } - } - else { - BeanDefinitionBuilder groupProcessorBuilder; - if (StringUtils.hasText(element.getAttribute(EXPRESSION_ATTRIBUTE))) { - String expression = element.getAttribute(EXPRESSION_ATTRIBUTE); - groupProcessorBuilder = - BeanDefinitionBuilder.genericBeanDefinition(ExpressionEvaluatingMessageGroupProcessor.class); - groupProcessorBuilder.addConstructorArgValue(expression); - } - else { - groupProcessorBuilder = - BeanDefinitionBuilder.genericBeanDefinition(DefaultAggregatingMessageGroupProcessor.class); - } - builder.addPropertyValue("processorBean", groupProcessorBuilder.getBeanDefinition()); - if (StringUtils.hasText(headersFunction)) { - groupProcessorBuilder.addPropertyReference("headersFunction", headersFunction); - } - } - - if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) { - String method = element.getAttribute(METHOD_ATTRIBUTE); - builder.addPropertyValue("methodName", method); - } - - doParse(builder, element, processor, parserContext); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, EXPIRE_GROUPS_UPON_COMPLETION); - - return builder; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AnnotationConfigParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AnnotationConfigParser.java deleted file mode 100644 index 3bd882b2b73..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AnnotationConfigParser.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.xml.BeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.core.type.MethodMetadata; -import org.springframework.integration.config.EnablePublisher; -import org.springframework.integration.config.IntegrationRegistrar; -import org.springframework.integration.config.PublisherRegistrar; -import org.springframework.integration.config.annotation.AnnotationMetadataAdapter; -import org.springframework.util.xml.DomUtils; - -/** - * Parser for the {@code } element of the integration namespace. - * Delegates the real configuration to the {@link IntegrationRegistrar}. - * If {@code } sub-element is present, the {@link PublisherRegistrar} - * is called, too. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - */ -public class AnnotationConfigParser implements BeanDefinitionParser { - - @Override - public @Nullable BeanDefinition parse(Element element, ParserContext parserContext) { - ExtendedAnnotationMetadata importingClassMetadata = new ExtendedAnnotationMetadata(element); - BeanDefinitionRegistry registry = parserContext.getRegistry(); - new IntegrationRegistrar() - .registerBeanDefinitions(importingClassMetadata, registry); - if (DomUtils.getChildElementByTagName(element, "enable-publisher") != null) { - new PublisherRegistrar() - .registerBeanDefinitions(importingClassMetadata, registry); - } - return null; - } - - private static final class ExtendedAnnotationMetadata extends AnnotationMetadataAdapter { - - private final Element element; - - ExtendedAnnotationMetadata(Element element) { - this.element = element; - } - - @Override - public @Nullable Map getAnnotationAttributes(String annotationType) { - if (EnablePublisher.class.getName().equals(annotationType)) { - Element enablePublisherElement = DomUtils.getChildElementByTagName(this.element, "enable-publisher"); - if (enablePublisherElement != null) { - Map attributes = new HashMap<>(); - attributes.put("defaultChannel", enablePublisherElement.getAttribute("default-publisher-channel")); - attributes.put("proxyTargetClass", enablePublisherElement.getAttribute("proxy-target-class")); - attributes.put("order", enablePublisherElement.getAttribute("order")); - return attributes; - } - else { - return null; - } - } - else { - return null; - } - } - - @Override - public Set getDeclaredMethods() { - throw new UnsupportedOperationException("The class doesn't support this operation"); - } - - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ApplicationEventMulticasterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ApplicationEventMulticasterParser.java deleted file mode 100644 index 15d245172d0..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ApplicationEventMulticasterParser.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.context.event.SimpleApplicationEventMulticaster; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.util.StringUtils; - -/** - * Parser for the <application-event-multicaster> element of the - * integration namespace. - * - * @author Mark Fisher - * @author Artem Bilan - */ -public class ApplicationEventMulticasterParser extends AbstractSingleBeanDefinitionParser { - - @Override - protected Class getBeanClass(Element element) { - return SimpleApplicationEventMulticaster.class; - } - - @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) - throws BeanDefinitionStoreException { - - return AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME; - } - - @Override - protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String taskExecutorRef = element.getAttribute("task-executor"); - if (StringUtils.hasText(taskExecutorRef)) { - builder.addPropertyReference("taskExecutor", taskExecutorRef); - } - else { - BeanDefinitionBuilder executorBuilder = - BeanDefinitionBuilder.genericBeanDefinition(ThreadPoolTaskExecutor.class); - executorBuilder.addPropertyValue("corePoolSize", 1); // NOSONAR - executorBuilder.addPropertyValue("maxPoolSize", 10); // NOSONAR - executorBuilder.addPropertyValue("queueCapacity", 0); // NOSONAR - executorBuilder.addPropertyValue("threadNamePrefix", "event-multicaster-"); - builder.addPropertyValue("taskExecutor", executorBuilder.getBeanDefinition()); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BarrierParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BarrierParser.java deleted file mode 100644 index b250a724928..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BarrierParser.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.aggregator.BarrierMessageHandler; -import org.springframework.util.StringUtils; - -/** - * Parser for {@code }. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.2 - */ -public class BarrierParser extends AbstractConsumerEndpointParser { - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(BarrierMessageHandler.class); - handlerBuilder.addConstructorArgValue(element.getAttribute("timeout")); - String triggerTimeout = element.getAttribute("trigger-timeout"); - if (StringUtils.hasText(triggerTimeout)) { - handlerBuilder.addConstructorArgValue(triggerTimeout); - } - String processor = element.getAttribute("output-processor"); - if (StringUtils.hasText(processor)) { - handlerBuilder.addConstructorArgReference(processor); - } - IntegrationNamespaceUtils.injectConstructorWithAdapter("correlation-strategy", - "correlation-strategy-method", "correlation-strategy-expression", - "CorrelationStrategy", element, handlerBuilder, null, parserContext); - IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "requires-reply"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(handlerBuilder, element, "discard-channel"); - return handlerBuilder; - } - -} - diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BeanDefinitionRegisteringParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BeanDefinitionRegisteringParser.java deleted file mode 100644 index 3c5fb2a9689..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BeanDefinitionRegisteringParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.xml.ParserContext; - -/** - * Simple strategy interface for parsers that are responsible - * for parsing an element, creating a bean definition, and then - * registering the bean. The {@link #parse(Element, ParserContext)} - * method should return the name of the registered bean. - * - * @author Mark Fisher - */ -public interface BeanDefinitionRegisteringParser { - - String parse(Element element, ParserContext parserContext); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BridgeParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BridgeParser.java deleted file mode 100644 index caffea1b3a7..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/BridgeParser.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.handler.BridgeHandler; - -/** - * Parser for the <bridge> element. - * - * @author Mark Fisher - */ -public class BridgeParser extends AbstractConsumerEndpointParser { - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(BridgeHandler.class); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - return builder; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChainParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChainParser.java deleted file mode 100644 index f2a783eaa14..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChainParser.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.IntegrationConfigUtils; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.handler.MessageHandlerChain; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Parser for the <chain> element. - * - * @author Mark Fisher - * @author Iwein Fuld - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Gunnar Hillert - * @author Gary Russell - * @author Ngoc Nhan - */ -public class ChainParser extends AbstractConsumerEndpointParser { - - /** - * {@link BeanDefinition} attribute used to pass down the current bean id for nested chains, allowing full - * qualification of 'named' handlers within nested chains. - * - */ - private static final String SI_CHAIN_NESTED_ID_ATTRIBUTE = "SI.ChainParser.NestedId.Prefix"; - - private final Log logger = LogFactory.getLog(this.getClass()); - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MessageHandlerChain.class); - - if (!StringUtils.hasText(element.getAttribute(ID_ATTRIBUTE))) { - this.logger.info("It is useful to provide an explicit 'id' attribute on 'chain' elements " + - "to simplify the identification of child elements in logs etc."); - } - - String chainHandlerId = this.resolveId(element, builder.getRawBeanDefinition(), parserContext); - List handlerList = new ManagedList<>(); - Set handlerBeanNameSet = new HashSet<>(); - NodeList children = element.getChildNodes(); - - int childOrder = 0; - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE && !"poller".equals(child.getLocalName())) { - BeanMetadataElement childBeanMetadata = this.parseChild(chainHandlerId, (Element) child, childOrder++, - parserContext, builder.getBeanDefinition()); - if (childBeanMetadata instanceof RuntimeBeanReference runtimeBeanReference) { - String handlerBeanName = runtimeBeanReference.getBeanName(); - if (!handlerBeanNameSet.add(handlerBeanName)) { - parserContext.getReaderContext().error("A bean definition is already registered for " + - "beanName: '" + handlerBeanName + "' within the current .", - element); - } - } - if ("gateway".equals(child.getLocalName())) { - BeanDefinitionBuilder gwBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationContextUtils.BASE_PACKAGE + ".gateway.RequestReplyMessageHandlerAdapter"); - gwBuilder.addConstructorArgValue(childBeanMetadata); - handlerList.add(gwBuilder.getBeanDefinition()); - } - else { - handlerList.add(Objects.requireNonNull(childBeanMetadata)); - } - } - } - builder.addPropertyValue("handlers", handlerList); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - return builder; - } - - @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { - String id = super.resolveId(element, definition, parserContext); - BeanDefinition containingBeanDefinition = parserContext.getContainingBeanDefinition(); - if (containingBeanDefinition != null) { - String nestedChainIdPrefix = (String) containingBeanDefinition.getAttribute(SI_CHAIN_NESTED_ID_ATTRIBUTE); - if (StringUtils.hasText(nestedChainIdPrefix)) { - id = nestedChainIdPrefix + "$child." + id; - } - } - definition.setAttribute(SI_CHAIN_NESTED_ID_ATTRIBUTE, id); - return id; - } - - private BeanMetadataElement parseChild(String chainHandlerId, Element element, int order, ParserContext parserContext, - BeanDefinition parentDefinition) { - - BeanDefinitionHolder holder = null; - - String id = element.getAttribute(ID_ATTRIBUTE); - boolean hasId = StringUtils.hasText(id); - String handlerComponentName = chainHandlerId + "$child" + (hasId ? "." + id : "#" + order); - - if ("bean".equals(element.getLocalName())) { - holder = parserContext.getDelegate().parseBeanDefinitionElement(element, parentDefinition); - } - else { - - this.validateChild(element, parserContext); - - BeanDefinition beanDefinition = parserContext.getDelegate().parseCustomElement(element, parentDefinition); - if (beanDefinition == null) { - parserContext.getReaderContext().error("child BeanDefinition must not be null", element); - // This exception is meant to signal to NullAway that the error method throws an exception - throw new IllegalStateException("Child BeanDefinition must not be null"); - } - else { - holder = new BeanDefinitionHolder(beanDefinition, handlerComponentName + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX); - } - } - - Objects.requireNonNull(holder).getBeanDefinition().getPropertyValues().add("componentName", handlerComponentName); // NOSONAR never null - - if (hasId) { - BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry()); - return new RuntimeBeanReference(holder.getBeanName()); - } - - return holder; - } - - private void validateChild(Element element, ParserContext parserContext) { - - final Object source = parserContext.extractSource(element); - - final String order = element.getAttribute(IntegrationNamespaceUtils.ORDER); - - if (StringUtils.hasText(order)) { - parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(element) + " must not define " + - "an 'order' attribute when used within a chain.", source); - } - - final List pollerChildElements = DomUtils.getChildElementsByTagName(element, "poller"); - - if (!pollerChildElements.isEmpty()) { - parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(element) + " must not define " + - "a 'poller' sub-element when used within a chain.", source); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInterceptorParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInterceptorParser.java deleted file mode 100644 index bf2413b7989..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInterceptorParser.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; -import org.springframework.beans.factory.xml.ParserContext; - -/** - * A helper class for parsing the sub-elements of a channel's - * interceptors element. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Ngoc Nhan - */ -public class ChannelInterceptorParser { - - private final Map parsers; - - public ChannelInterceptorParser() { - this.parsers = new HashMap<>(); - this.parsers.put("wire-tap", new WireTapParser()); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public ManagedList parseInterceptors(Element element, ParserContext parserContext) { - ManagedList interceptors = new ManagedList(); - NodeList childNodes = element.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element childElement = (Element) child; - String localName = child.getLocalName(); - if ("bean".equals(localName)) { - BeanDefinitionParserDelegate delegate = parserContext.getDelegate(); - BeanDefinitionHolder holder = delegate.parseBeanDefinitionElement(childElement); - holder = delegate.decorateBeanDefinitionIfRequired(childElement, Objects.requireNonNull(holder)); - parserContext.registerBeanComponent(new BeanComponentDefinition(holder)); - interceptors.add(new RuntimeBeanReference(holder.getBeanName())); - } - else if ("ref".equals(localName)) { - String ref = childElement.getAttribute("bean"); - interceptors.add(new RuntimeBeanReference(ref)); - } - else { - BeanDefinitionRegisteringParser parser = this.parsers.get(localName); - if (parser == null) { - parserContext.getReaderContext().error( - "unsupported interceptor element '" + localName + "'", childElement); - // Redundant Exception is here to satisfy NullAway warning parser.parse statement below. - throw new IllegalStateException("unsupported interceptor element '" + localName + "'"); - } - String interceptorBeanName = parser.parse(childElement, parserContext); - interceptors.add(new RuntimeBeanReference(interceptorBeanName)); - } - } - } - return interceptors; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ClaimCheckInParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ClaimCheckInParser.java deleted file mode 100644 index ecc337ad538..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ClaimCheckInParser.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.transformer.ClaimCheckInTransformer; -import org.springframework.util.Assert; - -/** - * Parser for the <claim-check-in/> element. - * - * @author Mark Fisher - * @since 2.0 - */ -public class ClaimCheckInParser extends AbstractTransformerParser { - - @Override - protected String getTransformerClassName() { - return ClaimCheckInTransformer.class.getName(); - } - - @Override - protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String messageStoreRef = element.getAttribute("message-store"); - Assert.hasText(messageStoreRef, "The 'message-store' attribute is required."); - builder.addConstructorArgReference(messageStoreRef); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ClaimCheckOutParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ClaimCheckOutParser.java deleted file mode 100644 index 41f8519cab1..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ClaimCheckOutParser.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.transformer.ClaimCheckOutTransformer; -import org.springframework.util.Assert; - -/** - * Parser for the <claim-check-out/> element. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @since 2.0 - */ -public class ClaimCheckOutParser extends AbstractTransformerParser { - - @Override - protected String getTransformerClassName() { - return ClaimCheckOutTransformer.class.getName(); - } - - @Override - protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String messageStoreRef = element.getAttribute("message-store"); - Assert.hasText(messageStoreRef, "The 'message-store' attribute is required."); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remove-message"); - builder.addConstructorArgReference(messageStoreRef); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ControlBusParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ControlBusParser.java deleted file mode 100644 index f22a5e982fa..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ControlBusParser.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.ControlBusFactoryBean; - -/** - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Artem Bilan - * - * @since 2.0 - */ -public class ControlBusParser extends AbstractConsumerEndpointParser { - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ControlBusFactoryBean.class); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order"); - return builder; - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java deleted file mode 100644 index 71c76aa9206..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -public class ConverterParser extends AbstractBeanDefinitionParser { - - @Override - protected @Nullable AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - BeanDefinitionRegistry registry = parserContext.getRegistry(); - BeanComponentDefinition converterDefinition = - IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext); - if (converterDefinition != null) { - registerConverter(registry, converterDefinition); - } - else { - String beanName = element.getAttribute("ref"); - Assert.isTrue(StringUtils.hasText(beanName), - "Either a 'ref' attribute pointing to a Converter " + - "or a sub-element defining a Converter is required."); - registerConverter(registry, new RuntimeBeanReference(beanName)); - } - return null; - } - - private static void registerConverter(BeanDefinitionRegistry registry, BeanMetadataElement targetBeanDefinition) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition( - IntegrationContextUtils.BASE_PACKAGE + - ".config.ConverterRegistrar.IntegrationConverterRegistration") - .addConstructorArgValue(targetBeanDefinition); - BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultInboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultInboundChannelAdapterParser.java deleted file mode 100644 index a5cf7aeaba9..00000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultInboundChannelAdapterParser.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.config.xml; - -import java.util.List; -import java.util.Objects; - -import org.jspecify.annotations.Nullable; -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.ExpressionFactoryBean; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.endpoint.ExpressionEvaluatingMessageSource; -import org.springframework.integration.endpoint.MethodInvokingMessageSource; -import org.springframework.integration.expression.DynamicExpression; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * Parser for the <inbound-channel-adapter/> element. - * - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - */ -public class DefaultInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { - - @Override - protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { - Object source = parserContext.extractSource(element); - BeanMetadataElement result = null; - BeanComponentDefinition innerBeanDef = - IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext); - String sourceRef = element.getAttribute(IntegrationNamespaceUtils.REF_ATTRIBUTE); - String methodName = element.getAttribute(IntegrationNamespaceUtils.METHOD_ATTRIBUTE); - String expressionString = element.getAttribute(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE); - Element scriptElement = DomUtils.getChildElementByTagName(element, "script"); - Element expressionElement = DomUtils.getChildElementByTagName(element, "expression"); - - boolean hasInnerDef = innerBeanDef != null; - boolean hasRef = StringUtils.hasText(sourceRef); - boolean hasExpression = StringUtils.hasText(expressionString); - boolean hasScriptElement = scriptElement != null; - boolean hasExpressionElement = expressionElement != null; - boolean hasMethod = StringUtils.hasText(methodName); - - if (!hasInnerDef && !hasRef && !hasExpression && !hasScriptElement && !hasExpressionElement) { - parserContext.getReaderContext().error( - "Exactly one of the 'ref', 'expression', inner bean, - - - - - - - - - - - - Twitter - - - - - - - - - - - - -

- - - - -
-
- - -
-
- - - - - - - - -
-
-

See who’s here

-
    -
  • - - -
    -
    - nate_robinson -
    Nate Robinson 
    - @nate_robinson - Rain city ( word aapp ) | in Sports -
    - -
    -
    Recently tweeted:
    -
    What up y'all ?
    -
    about 10 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - ev -
    Evan Williams 
    - @ev - San Francisco, CA, US | in Business -
    - -
    -
    Recently tweeted:
    -
    @jeanpaul thanks, man.
    -
    7:28 PM Jan 18th
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - nytimes -
    The New York Times 
    - @nytimes - New York, NY | in News -
    - -
    -
    Recently tweeted:
    -
    The Caucus: Chinese President May Get Earful From Lawmakers https://nyti.ms/gVqqut
    -
    20 minutes ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - therealKDUB20 -
    Kyle Wilson 
    - @therealKDUB20 - New York | in Staff Picks: NFL Playoffs -
    - -
    -
    Recently tweeted:
    -
    @tonylogan85 school start yet?
    -
    1:22 PM Jan 18th
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - Oxfam -
    Oxfam International 
    - @Oxfam - in Charity -
    - -
    -
    Recently tweeted:
    -
    Oxfam calls on international donors to fund upcoming @UN $51mn appeal for #SriLanka #floods https://oxf.am/ZMm
    -
    about 2 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - grantimahara -
    Grant Imahara 
    - @grantimahara - San Francisco, CA | in Science -
    - -
    -
    Recently tweeted:
    -
    @bergopolis Ha, thanks!!
    -
    about 8 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - chelseahandler -
    Chelsea Handler 
    - @chelseahandler - Los Angeles, CA | in Entertainment -
    - -
    -
    Recently tweeted:
    -
    @qnzfrogy03 sorry about that. Makes me very happy to send gays and dogs
    -
    about 8 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - kevin_nealon -
    Kevin Nealon 
    - @kevin_nealon - Los Angeles, Ca. | in Funny -
    - -
    -
    Recently tweeted:
    -
    People on Segways are show-offs.
    -
    about 14 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - healthfinder -
    healthfinder.gov 
    - @healthfinder - Washington, DC | in Health -
    - -
    -
    Recently tweeted:
    -
    Fight the #flu. Stay away from people who are sick, wash your hands, and get enough sleep. More quick tips: https://bit.ly/dN812J.
    -
    about 17 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - kevinrose -
    Kevin Rose 
    - @kevinrose - San Francisco, CA | in Technology -
    - -
    -
    Recently tweeted:
    -
    RT @TheOnion: BREAKING #NEWS: Chinese President Hu Jintao Pays For #StateDinner While President Obama In Bathroom
    -
    about 13 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - azizansari -
    Aziz Ansari 
    - @azizansari - Los Angeles, CA | in Staff Picks -
    - -
    -
    Recently tweeted:
    -
    Hey tomorrow I am doing a Twitter Q&A to celebrate the return of Parks & Rec! Starts at noon PST/3pm EST til 4. Use hashtag #AskAziz.
    -
    about 13 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - SimpleMom -
    Tsh Oxenreider 
    - @SimpleMom - Austin | in Family -
    - -
    -
    Recently tweeted:
    -
    @SandyCoughlinRE Glad you like it! I've been thinking about adding Twitterfeed to it, but I don't want to bomboard people w/ my links...
    -
    43 minutes ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - BoltBus -
    BoltBus 
    - @BoltBus - Northeast | in Travel -
    - -
    -
    Recently tweeted:
    -
    @drjwalk All customers received a text message or email. Perhaps you gave the wrong email? Please contact customer service at 1-877-BOLTBUS.
    -
    about 21 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - ArchRecord -
    Architectural Record 
    - @ArchRecord - New York City | in Art & Design -
    - -
    -
    Recently tweeted:
    -
    Our latest Newsmaker Interview with #AIA Gold Medal Winner Fumihiko Maki https://www.architecturalrecord.com/news/newsmakers/1101fumihiko_maki.asp?WT.mc_id=twitter_archrecord?WT.mc_id=twitter_archrecord
    -
    about 22 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - gtdguy -
    David Allen 
    - @gtdguy - Ojai, California | in Books -
    - -
    -
    Recently tweeted:
    -
    Can't help loving arriving in NYC, no matter the weather. Maybe the crispness, in many forms, on many levels.
    -
    8:55 PM Jan 18th
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - GaelGreene -
    Gael Greene 
    - @GaelGreene - New York City | in Food & Drink -
    - -
    -
    Recently tweeted:
    -
    @ProfMTH Yes,tourist class.The Seminar paid 4 us both & it was just 2 1/2 hours.So I was a good sport. Do u think they paid business 4 Ruth?
    -
    about 16 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - CoryBooker -
    Cory Booker 
    - @CoryBooker - Newark, NJ | in Politics -
    - -
    -
    Recently tweeted:
    -
    Thanks! I will. RT @GlenistraBR: Thats fine you have 24 whole hrs,b4 you miss a day of exercise, fit it in#letsmove
    -
    about 1 hour ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - amazondeals -
    Amazon.com Deals 
    - @amazondeals - Seattle, Washington | in Deals -
    - -
    -
    Recently tweeted:
    -
    Lightning Deal! $109.99 - TomTom XXL 540T 5-Inch Widescreen Portable GPS Navigator (Lifetime Traffic Edition) https://amzn.to/GoldboxDeals
    -
    about 1 hour ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - Support -
    Support 
    - @Support - Twitter HQ | in Twitter -
    - -
    -
    Recently tweeted:
    -
    Users may experience difficulty when filing a ticket in our Help Center or commenting on an article. We're working to resolve these issues.
    -
    about 14 hours ago
    -
    - -
     
    -
    -
  • -
  • - - -
    -
    - _BoF_ -
    Business of Fashion 
    - @_BoF_ - London, New York, Tokyo, Paris | in Fashion -
    - -
    -
    Recently tweeted:
    -
    @fashedatlarge Love Chisou also, the original location on Princes St near Hanover Sq. Have you tried the Horenso Salad? It's amazing.
    -
    about 1 hour ago
    -
    - -
     
    -
    -
  • - -
-
Friends and industry peers you know. Celebrities you watch. Businesses you frequent. Find them all on Twitter.
-
-
- - -
-
-
-

Top Tweets View all

- - - - - -
- -
-
- - -
-
- - - -
-
-
Trending right now:
- -
- Why? - - Source: What the Trend? -
-
-
 
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java deleted file mode 100644 index 2b0038b84d3..00000000000 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mail.dsl; - -import java.io.Closeable; -import java.util.Properties; - -import com.icegreen.greenmail.util.GreenMail; -import com.icegreen.greenmail.util.GreenMailUtil; -import com.icegreen.greenmail.util.ServerSetup; -import com.icegreen.greenmail.util.ServerSetupTest; -import jakarta.mail.Flags; -import jakarta.mail.Folder; -import jakarta.mail.internet.AddressException; -import jakarta.mail.internet.InternetAddress; -import jakarta.mail.internet.MimeMessage; -import jakarta.mail.internet.MimeMessage.RecipientType; -import jakarta.mail.search.AndTerm; -import jakarta.mail.search.FlagTerm; -import jakarta.mail.search.FromTerm; -import jakarta.mail.search.SearchTerm; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.MessageChannels; -import org.springframework.integration.mail.ImapIdleChannelAdapter; -import org.springframework.integration.mail.MailHeaders; -import org.springframework.integration.mail.support.DefaultMailHeaderMapper; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Alexander Pinske - */ -@SpringJUnitConfig -@DirtiesContext -public class MailTests { - - private static GreenMail mailServer; - - @BeforeAll - public static void setup() { - ServerSetup smtp = ServerSetupTest.SMTP.dynamicPort(); - smtp.setServerStartupTimeout(10000); - ServerSetup imap = ServerSetupTest.IMAP.dynamicPort(); - imap.setServerStartupTimeout(10000); - ServerSetup pop3 = ServerSetupTest.POP3.dynamicPort(); - pop3.setServerStartupTimeout(10000); - mailServer = new GreenMail(new ServerSetup[] {smtp, pop3, imap}); - mailServer.setUser("bar@baz", "smtpuser", "pw"); - mailServer.setUser("popuser", "pw"); - mailServer.setUser("imapuser", "pw"); - mailServer.setUser("imapidleuser", "pw"); - mailServer.start(); - } - - @AfterAll - static void tearDown() { - mailServer.stop(); - } - - @Autowired - private MessageChannel sendMailChannel; - - @Autowired - @Qualifier("sendMailEndpoint.handler") - private MessageHandler sendMailHandler; - - @Autowired - private PollableChannel pop3Channel; - - @Autowired - private PollableChannel imapChannel; - - @Autowired - private PollableChannel imapIdleChannel; - - @Autowired - private ImapIdleChannelAdapter imapIdleAdapter; - - @Test - public void testSmtp() throws Exception { - assertThat(TestUtils.getPropertyValue(this.sendMailHandler, "mailSender.host")).isEqualTo("localhost"); - - Properties javaMailProperties = TestUtils.getPropertyValue(this.sendMailHandler, - "mailSender.javaMailProperties", Properties.class); - assertThat(javaMailProperties.getProperty("mail.debug")).isEqualTo("false"); - - this.sendMailChannel.send(MessageBuilder.withPayload("foo").build()); - - mailServer.waitForIncomingEmail(10000, 1); - - assertThat(mailServer.getReceivedMessagesForDomain("baz").length > 0).isTrue(); - MimeMessage message = mailServer.getReceivedMessagesForDomain("baz")[0]; - assertThat(message.getFrom()).containsOnly(new InternetAddress("foo@bar")); - assertThat(message.getRecipients(RecipientType.TO)).containsOnly(new InternetAddress("bar@baz")); - assertThat(message.getSubject()).isEqualTo("foo"); - assertThat(message.getContent()).asString().isEqualTo("foo"); - - } - - @Test - public void testPop3() throws Exception { - MimeMessage mimeMessage = - GreenMailUtil.createTextEmail("Foo ", "Bar , Bar2 ", "Test Email", - "foo\r\n", mailServer.getPop3().getServerSetup()); - mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); - mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); - mailServer.getUserManager().getUser("popuser").deliver(mimeMessage); - - Message message = this.pop3Channel.receive(10000); - assertThat(message).isNotNull(); - MessageHeaders headers = message.getHeaders(); - assertThat(headers.get(MailHeaders.TO, String[].class)).containsExactly("Foo "); - assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar ,Bar2 "); - assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); - assertThat(message.getPayload()).isEqualTo("foo\r\n"); - assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isFalse(); - } - - @Test - public void testImap() throws Exception { - MimeMessage mimeMessage = - GreenMailUtil.createTextEmail("Foo ", "Bar ", "Test Email", "foo\r\n", - mailServer.getImap().getServerSetup()); - mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); - mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); - mailServer.getUserManager().getUser("imapuser").deliver(mimeMessage); - - Message message = this.imapChannel.receive(10000); - assertThat(message).isNotNull(); - MimeMessage mm = (MimeMessage) message.getPayload(); - assertThat(mm.getRecipients(RecipientType.TO)[0].toString()).isEqualTo("Foo "); - assertThat(mm.getFrom()[0].toString()).isEqualTo("Bar "); - assertThat(mm.getSubject()).isEqualTo("Test Email"); - assertThat(mm.getContent()).isEqualTo("foo\r\n"); - assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); - message.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, Closeable.class).close(); - } - - @Test - public void testImapIdle() throws Exception { - MimeMessage mimeMessage = - GreenMailUtil.createTextEmail("Foo ", "Bar ", "Test Email", "foo\r\n", - mailServer.getImap().getServerSetup()); - mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); - mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); - mailServer.getUserManager().getUser("imapidleuser").deliver(mimeMessage); - - Message message = this.imapIdleChannel.receive(10000); - assertThat(message).isNotNull(); - MessageHeaders headers = message.getHeaders(); - assertThat(headers.get(MailHeaders.TO, String[].class)).containsExactly("Foo "); - assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar "); - assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); - assertThat(message.getPayload()).isEqualTo("foo\r\n"); - assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); - this.imapIdleAdapter.stop(); - assertThat(TestUtils.getPropertyValue(this.imapIdleAdapter, "shouldReconnectAutomatically", Boolean.class)) - .isFalse(); - } - - @Configuration - @EnableIntegration - public static class ContextConfiguration { - - @Bean - public IntegrationFlow sendMailFlow() { - return IntegrationFlow.from("sendMailChannel") - .enrichHeaders(Mail.headers() - .subjectFunction(m -> "foo") - .from("foo@bar") - .toFunction(m -> new String[] {"bar@baz"})) - .handle(Mail.outboundAdapter("localhost") - .port(mailServer.getSmtp().getPort()) - .credentials("smtpuser", "pw") - .protocol("smtp") - .javaMailProperties(p -> p.put("mail.debug", "false")), - e -> e.id("sendMailEndpoint")) - .get(); - } - - @Bean - public IntegrationFlow pop3MailFlow() { - return IntegrationFlow - .from(Mail.pop3InboundAdapter("localhost", mailServer.getPop3().getPort(), "popuser", "pw") - .javaMailProperties(p -> p.put("mail.debug", "false")) - .autoCloseFolder(true) - .headerMapper(mailHeaderMapper()), - e -> e.autoStartup(true).poller(p -> p.fixedDelay(1000))) - .enrichHeaders(s -> s.headerExpressions(c -> c.put(MailHeaders.SUBJECT, "payload.subject") - .put(MailHeaders.FROM, "payload.from[0].toString()"))) - .channel(MessageChannels.queue("pop3Channel")) - .get(); - } - - @Bean - public IntegrationFlow imapMailFlow() { - return IntegrationFlow - .from(Mail.imapInboundAdapter("imap://imapuser:pw@localhost:" + mailServer.getImap().getPort() + "/INBOX") - .searchTermStrategy(this::fromAndNotSeenTerm) - .userFlag("testSIUserFlag") - .autoCloseFolder(false) - .simpleContent(true) - .javaMailProperties(p -> p.put("mail.debug", "false")), - e -> e.autoStartup(true) - .poller(p -> p.fixedDelay(1000))) - .channel(MessageChannels.queue("imapChannel")) - .get(); - } - - @Bean - public IntegrationFlow imapIdleFlow() { - return IntegrationFlow - .from(Mail.imapIdleAdapter("imap://imapidleuser:pw@localhost:" + mailServer.getImap().getPort() + "/INBOX") - .autoStartup(true) - .searchTermStrategy(this::fromAndNotSeenTerm) - .userFlag("testSIUserFlag") - .autoCloseFolder(false) - .javaMailProperties(p -> p.put("mail.debug", "false") - .put("mail.imap.connectionpoolsize", "5")) - .shouldReconnectAutomatically(false) - .simpleContent(true) - .headerMapper(mailHeaderMapper())) - .channel(MessageChannels.queue("imapIdleChannel")) - .get(); - } - - @Bean - public HeaderMapper mailHeaderMapper() { - return new DefaultMailHeaderMapper(); - } - - private SearchTerm fromAndNotSeenTerm(Flags supportedFlags, Folder folder) { - try { - FromTerm fromTerm = new FromTerm(new InternetAddress("bar@baz")); - return new AndTerm(fromTerm, new FlagTerm(new Flags(Flags.Flag.SEEN), false)); - } - catch (AddressException e) { - throw new RuntimeException(e); - } - - } - - } - -} diff --git a/spring-integration-mail/src/test/resources/log4j2-test.xml b/spring-integration-mail/src/test/resources/log4j2-test.xml deleted file mode 100644 index e4260c56bf8..00000000000 --- a/spring-integration-mail/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mail/src/test/resources/test.mail b/spring-integration-mail/src/test/resources/test.mail deleted file mode 100644 index 9e8349f8a52..00000000000 --- a/spring-integration-mail/src/test/resources/test.mail +++ /dev/null @@ -1,50 +0,0 @@ -Delivered-To: yyyyy@gmail.com -Received: by 10.64.27.133 with SMTP id t5csp748412ieg; - Thu, 1 Nov 2012 09:38:30 -0700 (PDT) -Received: by 10.220.40.16 with SMTP id i16mr23706195vce.31.1351787910470; - Thu, 01 Nov 2012 09:38:30 -0700 (PDT) -Return-Path: -Received: from mail-vb0-f51.google.com (mail-vb0-f51.google.com [209.85.212.51]) - by mx.google.com with ESMTPS id t8si2299833vcw.15.2012.11.01.09.38.30 - (version=TLSv1/SSLv3 cipher=OTHER); - Thu, 01 Nov 2012 09:38:30 -0700 (PDT) -Received-SPF: pass (google.com: domain of xxxxx@gmail.com designates 209.85.212.51 as permitted sender) client-ip=209.85.212.51; -Authentication-Results: mx.google.com; spf=pass (google.com: domain of xxxxxx@gmail.com designates 209.85.212.51 as permitted sender) smtp.mail=xxxxx@gmail.com; dkim=pass header.i=@gmail.com -Received: by mail-vb0-f51.google.com with SMTP id fn1so2898461vbb.24 - for ; Thu, 01 Nov 2012 09:38:30 -0700 (PDT) -Received: by 10.220.227.70 with SMTP id iz6mr23760024vcb.45.1351787910197; - Thu, 01 Nov 2012 09:38:30 -0700 (PDT) -Return-Path: -Received: from [192.168.1.7] (pool-72-78-102-80.phlapa.fios.verizon.net. [72.78.102.80]) - by mx.google.com with ESMTPS id g5sm3780201vez.6.2012.11.01.09.38.29 - (version=TLSv1/SSLv3 cipher=OTHER); - Thu, 01 Nov 2012 09:38:29 -0700 (PDT) -Sender: Gary Russell -Message-ID: <5092A55B.8050000@foo.bar> -Date: Thu, 01 Nov 2012 12:37:47 -0400 -From: Gary Russell -User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:16.0) Gecko/20121026 Thunderbird/16.0.2 -MIME-Version: 1.0 -To: yyyyy@gmail.com -Subject: Test -Content-Type: multipart/mixed; - boundary="------------040903000701040401040200" - -This is a multi-part message in MIME format. ---------------040903000701040401040200 -Content-Type: text/plain; charset=ISO-8859-1; format=flowed -Content-Transfer-Encoding: 7bit - -bar - ---------------040903000701040401040200 -Content-Type: text/plain; charset=UTF-8; - name="foo.txt" -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; - filename="foo.txt" - -foo - ---------------040903000701040401040200-- - diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java deleted file mode 100644 index 3fe07bef177..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.mongodb.inbound.MongoDbMessageSource; - -/** - * Parser for MongoDb store inbound adapters. - * - * @author Amol Nayak - * @author Oleg Zhurakousky - * @author Artem Bilan - * - * @since 2.2 - */ -public class MongoDbInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { - - @Override - protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MongoDbMessageSource.class); - - // Will parse and validate 'mongodb-template', 'mongodb-factory', - // 'collection-name', 'collection-name-expression' and 'mongo-converter' - MongoParserUtils.processCommonAttributes(element, parserContext, builder); - - BeanDefinition queryExpressionDef = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("query", "query-expression", - parserContext, element, true); - - builder.addConstructorArgValue(queryExpressionDef); - - BeanDefinition expressionDef = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("update", "update-expression", - parserContext, element, false); - builder.addPropertyValue("updateExpression", expressionDef); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "entity-class"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-single-result"); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java deleted file mode 100644 index dad234ebddf..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * Namespace handler for Spring Integration's 'mongodb' namespace. - * - * @author Amol Nayak - * @author Oleg Zhurakousky - * - * @since 2.2 - */ -public class MongoDbNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - public void init() { - registerBeanDefinitionParser("inbound-channel-adapter", new MongoDbInboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-channel-adapter", new MongoDbOutboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-gateway", new MongoDbOutboundGatewayParser()); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParser.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParser.java deleted file mode 100644 index 733a827a4f5..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParser.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.mongodb.outbound.MongoDbStoringMessageHandler; - -/** - * Parser for Mongodb store outbound adapters. - * - * @author Oleg Zhurakousky - * - * @since 2.2 - */ -public class MongoDbOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MongoDbStoringMessageHandler.class); - - // Will parse and validate 'mongodb-template', 'mongodb-factory', - // 'collection-name', 'collection-name-expression' and 'mongo-converter' - MongoParserUtils.processCommonAttributes(element, parserContext, builder); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java deleted file mode 100644 index fb9f9944837..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway; -import org.springframework.util.StringUtils; - -/** - * Parser for MongoDb outbound gateways. - * - * @author Xavier Padro - * @author Artem Bilan - * - * @since 5.0 - */ -public class MongoDbOutboundGatewayParser extends AbstractConsumerEndpointParser { - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - final BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(MongoDbOutboundGateway.class); - - MongoParserUtils.processCommonAttributes(element, parserContext, builder); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout"); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); - String collectionCallback = element.getAttribute("collection-callback"); - - if (StringUtils.hasText(collectionCallback)) { - if (StringUtils.hasText(element.getAttribute("query")) || - StringUtils.hasText(element.getAttribute("query-expression"))) { - - parserContext.getReaderContext() - .error("'collection-callback' is not allowed with 'query' or 'query-expression'", element); - } - - builder.addPropertyReference("messageCollectionCallback", collectionCallback); - } - else { - BeanDefinition queryExpressionDef = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("query", - "query-expression", parserContext, element, true); - - if (queryExpressionDef != null) { - builder.addPropertyValue("queryExpression", queryExpressionDef); - } - } - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-single-result"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "entity-class"); - - return builder; - } - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java deleted file mode 100644 index 061d24c9468..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoParserUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.util.StringUtils; - -/** - * Utility class used by mongo parsers. - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.2 - */ -final class MongoParserUtils { - - private MongoParserUtils() { - } - - /** - * Will parse and validate - * 'mongodb-template', 'mongodb-factory', 'collection-name', 'collection-name-expression' and 'mongo-converter'. - * @param element the element to parse - * @param parserContext the context for parsing - * @param builder the bean definition builder - */ - public static void processCommonAttributes(Element element, ParserContext parserContext, - BeanDefinitionBuilder builder) { - - String mongoDbTemplate = element.getAttribute("mongo-template"); - String mongoDbFactory = element.getAttribute("mongodb-factory"); - - if (StringUtils.hasText(mongoDbTemplate) && StringUtils.hasText(mongoDbFactory)) { - parserContext.getReaderContext().error("Only one of '" + mongoDbTemplate + "' or '" - + mongoDbFactory + "' is allowed", element); - } - - if (StringUtils.hasText(mongoDbTemplate)) { - builder.addConstructorArgReference(mongoDbTemplate); - if (StringUtils.hasText(element.getAttribute("mongo-converter"))) { - parserContext.getReaderContext().error("'mongo-converter' is not allowed with 'mongo-template'", - element); - } - } - else { - if (!StringUtils.hasText(mongoDbFactory)) { - mongoDbFactory = "mongoDbFactory"; - } - builder.addConstructorArgReference(mongoDbFactory); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "mongo-converter"); - } - - BeanDefinition collectionNameExpressionDef = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("collection-name", - "collection-name-expression", parserContext, element, false); - - if (collectionNameExpressionDef != null) { - builder.addPropertyValue("collectionNameExpression", collectionNameExpressionDef); - } - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/package-info.java deleted file mode 100644 index 20f8ceabf2b..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Contains parser classes for the MongoDb namespace support. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.config; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/AbstractMongoDbMessageSourceSpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/AbstractMongoDbMessageSourceSpec.java deleted file mode 100644 index 66394113641..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/AbstractMongoDbMessageSourceSpec.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import java.util.function.Supplier; - -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.dsl.MessageSourceSpec; -import org.springframework.integration.expression.SupplierExpression; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.mongodb.inbound.AbstractMongoDbMessageSource; - -/** - * A {@link MessageSourceSpec} extension for common MongoDB sources options. - * - * @param target spec type. - * @param target message source type. - * - * @author Artem Bilan - * - * @since 5.5 - */ -public class AbstractMongoDbMessageSourceSpec, - H extends AbstractMongoDbMessageSource> - extends MessageSourceSpec { - - /** - * Allow you to set the type of the entityClass that will be passed to the MongoDB query method. - * Default is {@link com.mongodb.DBObject}. - * @param entityClass The entity class. - * @return the spec - * @see AbstractMongoDbMessageSource#setEntityClass(Class) - */ - public S entityClass(Class entityClass) { - this.target.setEntityClass(entityClass); - return _this(); - } - - /** - * Allow you to manage which find* method to invoke. - * @param expectSingleResult true if a single result is expected. - * @return the spec - * @see AbstractMongoDbMessageSource#setExpectSingleResult(boolean) - */ - public S expectSingleResult(boolean expectSingleResult) { - this.target.setExpectSingleResult(expectSingleResult); - return _this(); - } - - /** - * Configure a collection name to query against. - * @param collectionName the name of the MongoDb collection - * @return the spec - */ - public S collectionName(String collectionName) { - return collectionNameExpression(new LiteralExpression(collectionName)); - } - - /** - * Configure a SpEL expression to evaluation a collection name on each {@code receive()} call. - * @param collectionNameExpression the SpEL expression for name of the MongoDb collection - * @return the spec - */ - public S collectionNameExpression(String collectionNameExpression) { - return collectionNameExpression(PARSER.parseExpression(collectionNameExpression)); - } - - /** - * Configure a {@link Supplier} to obtain a collection name on each {@code receive()} call. - * @param collectionNameSupplier the {@link Supplier} for name of the MongoDb collection - * @return the spec - */ - public S collectionNameSupplier(Supplier collectionNameSupplier) { - return collectionNameExpression(new SupplierExpression<>(collectionNameSupplier)); - } - - /** - * Configure a SpEL expression to evaluation a collection name on each {@code receive()} call. - * @param collectionNameExpression the SpEL expression for name of the MongoDb collection - * @return the spec - * @see AbstractMongoDbMessageSource#setCollectionNameExpression(Expression) - */ - public S collectionNameExpression(Expression collectionNameExpression) { - this.target.setCollectionNameExpression(collectionNameExpression); - return _this(); - } - - /** - * Configure a custom {@link MongoConverter} used to assist in deserialization - * data read from MongoDb. - * @param mongoConverter The mongo converter. - * @return the spec - * @see AbstractMongoDbMessageSource#setMongoConverter(MongoConverter) - */ - public S mongoConverter(MongoConverter mongoConverter) { - this.target.setMongoConverter(mongoConverter); - return _this(); - } - - /** - * Configure a MongoDB update. - * @param update the MongoDB update. - * @return the spec - */ - public S update(String update) { - return update(new LiteralExpression(update)); - } - - /** - * Configure a MongoDB update. - * @param update the MongoDB update. - * @return the spec - */ - public S update(Update update) { - return update(new ValueExpression<>(update)); - } - - /** - * Configure a {@link Supplier} to produce a MongoDB update on each receive call. - * @param updateSupplier the {@link Supplier} for MongoDB update. - * @return the spec - */ - public S updateSupplier(Supplier updateSupplier) { - return update(new SupplierExpression<>(updateSupplier)); - } - - /** - * Configure a SpEL expression to evaluate a MongoDB update. - * @param updateExpression the expression to evaluate a MongoDB update. - * @return the spec - */ - public S update(Expression updateExpression) { - this.target.setUpdateExpression(updateExpression); - return _this(); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java deleted file mode 100644 index 9ddaad5f20a..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.mongodb.inbound.MongoDbChangeStreamMessageProducer; - -/** - * Factory class for building MongoDb components. - * - * @author Xavier Padro - * @author Artem Bilan - * - * @since 5.0 - */ -public final class MongoDb { - - /** - * Create a {@link MongoDbOutboundGatewaySpec} builder instance - * based on the provided {@link MongoDatabaseFactory} and {@link MongoConverter}. - * @param mongoDbFactory the {@link MongoDatabaseFactory} to use. - * @param mongoConverter the {@link MongoConverter} to use. - * @return the {@link MongoDbOutboundGatewaySpec} instance - */ - public static MongoDbOutboundGatewaySpec outboundGateway( - MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) { - - return new MongoDbOutboundGatewaySpec(mongoDbFactory, mongoConverter); - } - - /** - * Create a {@link MongoDbOutboundGatewaySpec} builder instance - * based on the provided {@link MongoOperations}. - * @param mongoTemplate the {@link MongoOperations} to use. - * @return the {@link MongoDbOutboundGatewaySpec} instance - */ - public static MongoDbOutboundGatewaySpec outboundGateway(MongoOperations mongoTemplate) { - return new MongoDbOutboundGatewaySpec(mongoTemplate); - } - - /** - * Create a {@link ReactiveMongoDbMessageHandlerSpec} builder instance - * based on the provided {@link ReactiveMongoDatabaseFactory}. - * @param mongoDbFactory the {@link ReactiveMongoDatabaseFactory} to use. - * @return the {@link MongoDbOutboundGatewaySpec} instance - * @since 5.3 - */ - public static ReactiveMongoDbMessageHandlerSpec reactiveOutboundChannelAdapter( - ReactiveMongoDatabaseFactory mongoDbFactory) { - - return new ReactiveMongoDbMessageHandlerSpec(mongoDbFactory); - } - - /** - * Create a {@link ReactiveMongoDbMessageHandlerSpec} builder instance - * based on the provided {@link ReactiveMongoOperations}. - * @param mongoTemplate the {@link ReactiveMongoOperations} to use. - * @return the {@link ReactiveMongoDbMessageHandlerSpec} instance - * @since 5.3 - */ - public static ReactiveMongoDbMessageHandlerSpec reactiveOutboundChannelAdapter( - ReactiveMongoOperations mongoTemplate) { - - return new ReactiveMongoDbMessageHandlerSpec(mongoTemplate); - } - - /** - * Create a {@link ReactiveMongoDbMessageSourceSpec} builder instance - * based on the provided {@link ReactiveMongoDatabaseFactory}. - * @param mongoDbFactory the {@link ReactiveMongoDatabaseFactory} to use. - * @param query the MongoDb query - * @return the {@link ReactiveMongoDbMessageSourceSpec} instance - * @since 5.3 - */ - public static ReactiveMongoDbMessageSourceSpec reactiveInboundChannelAdapter( - ReactiveMongoDatabaseFactory mongoDbFactory, String query) { - - return new ReactiveMongoDbMessageSourceSpec(mongoDbFactory, new LiteralExpression(query)); - } - - /** - * Create a {@link ReactiveMongoDbMessageSourceSpec} builder instance - * based on the provided {@link ReactiveMongoDatabaseFactory}. - * @param mongoDbFactory the {@link ReactiveMongoDatabaseFactory} to use. - * @param query the MongoDb query DSL object - * @return the {@link ReactiveMongoDbMessageSourceSpec} instance - * @since 5.3 - */ - public static ReactiveMongoDbMessageSourceSpec reactiveInboundChannelAdapter( - ReactiveMongoDatabaseFactory mongoDbFactory, Query query) { - - return new ReactiveMongoDbMessageSourceSpec(mongoDbFactory, new ValueExpression<>(query)); - } - - /** - * Create a {@link ReactiveMongoDbMessageSourceSpec} builder instance - * based on the provided {@link ReactiveMongoOperations}. - * @param mongoTemplate the {@link ReactiveMongoOperations} to use. - * @param query the MongoDb query - * @return the {@link ReactiveMongoDbMessageSourceSpec} instance - * @since 5.3 - */ - public static ReactiveMongoDbMessageSourceSpec reactiveInboundChannelAdapter(ReactiveMongoOperations mongoTemplate, - String query) { - - return new ReactiveMongoDbMessageSourceSpec(mongoTemplate, new LiteralExpression(query)); - } - - /** - * Create a {@link ReactiveMongoDbMessageSourceSpec} builder instance - * based on the provided {@link ReactiveMongoOperations}. - * @param mongoTemplate the {@link ReactiveMongoOperations} to use. - * @param query the MongoDb query DSL object - * @return the {@link ReactiveMongoDbMessageSourceSpec} instance - * @since 5.3 - */ - public static ReactiveMongoDbMessageSourceSpec reactiveInboundChannelAdapter(ReactiveMongoOperations mongoTemplate, - Query query) { - - return new ReactiveMongoDbMessageSourceSpec(mongoTemplate, new ValueExpression<>(query)); - } - - /** - * Create a {@link MongoDbChangeStreamMessageProducerSpec} builder instance - * based on the provided {@link ReactiveMongoOperations}. - * @param mongoOperations the {@link ReactiveMongoOperations} to use. - * @return the {@link MongoDbChangeStreamMessageProducerSpec} instance - * @since 5.3 - */ - public static MongoDbChangeStreamMessageProducerSpec changeStreamInboundChannelAdapter( - ReactiveMongoOperations mongoOperations) { - - return new MongoDbChangeStreamMessageProducerSpec(new MongoDbChangeStreamMessageProducer(mongoOperations)); - } - - /** - * Create a {@link MongoDbMessageSourceSpec} builder instance - * based on the provided {@link MongoDatabaseFactory}. - * @param mongoDbFactory the {@link MongoDatabaseFactory} to use. - * @param query the MongoDb query - * @return the {@link MongoDbMessageSourceSpec} instance - * @since 5.5 - */ - public static MongoDbMessageSourceSpec inboundChannelAdapter(MongoDatabaseFactory mongoDbFactory, String query) { - return new MongoDbMessageSourceSpec(mongoDbFactory, new LiteralExpression(query)); - } - - /** - * Create a {@link MongoDbMessageSourceSpec} builder instance - * based on the provided {@link MongoDatabaseFactory}. - * @param mongoDbFactory the {@link MongoDatabaseFactory} to use. - * @param query the MongoDb query DSL object - * @return the {@link MongoDbMessageSourceSpec} instance - * @since 5.5 - */ - public static MongoDbMessageSourceSpec inboundChannelAdapter(MongoDatabaseFactory mongoDbFactory, Query query) { - return new MongoDbMessageSourceSpec(mongoDbFactory, new ValueExpression<>(query)); - } - - /** - * Create a {@link MongoDbMessageSourceSpec} builder instance - * based on the provided {@link MongoOperations}. - * @param mongoTemplate the {@link MongoOperations} to use. - * @param query the MongoDb query - * @return the {@link MongoDbMessageSourceSpec} instance - * @since 5.5 - */ - public static MongoDbMessageSourceSpec inboundChannelAdapter(MongoOperations mongoTemplate, String query) { - return new MongoDbMessageSourceSpec(mongoTemplate, new LiteralExpression(query)); - } - - /** - * Create a {@link MongoDbMessageSourceSpec} builder instance - * based on the provided {@link MongoOperations}. - * @param mongoTemplate the {@link MongoOperations} to use. - * @param query the MongoDb query DSL object - * @return the {@link MongoDbMessageSourceSpec} instance - * @since 5.5 - */ - public static MongoDbMessageSourceSpec reactiveInboundChannelAdapter(MongoOperations mongoTemplate, Query query) { - return new MongoDbMessageSourceSpec(mongoTemplate, new ValueExpression<>(query)); - } - - private MongoDb() { - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbChangeStreamMessageProducerSpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbChangeStreamMessageProducerSpec.java deleted file mode 100644 index 52396ff28b7..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbChangeStreamMessageProducerSpec.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import org.springframework.data.mongodb.core.ChangeStreamOptions; -import org.springframework.integration.dsl.MessageProducerSpec; -import org.springframework.integration.mongodb.inbound.MongoDbChangeStreamMessageProducer; - -/** - * A {@link MessageProducerSpec} for tne {@link MongoDbChangeStreamMessageProducer}. - * - * @author Artem Bilan - * - * @since 5.3 - */ -public class MongoDbChangeStreamMessageProducerSpec - extends MessageProducerSpec { - - /** - * Construct a builder based on an initial {@link MongoDbChangeStreamMessageProducerSpec}. - * @param producer the {@link MongoDbChangeStreamMessageProducerSpec} to use. - */ - public MongoDbChangeStreamMessageProducerSpec(MongoDbChangeStreamMessageProducer producer) { - super(producer); - } - - /** - * Configure a domain type to convert change event body into. - * @param domainType the type to use. - * @return the spec. - */ - public MongoDbChangeStreamMessageProducerSpec domainType(Class domainType) { - this.target.setDomainType(domainType); - return this; - } - - /** - * Configure a collection to subscribe for change events. - * @param collection the collection to use. - * @return the spec. - */ - public MongoDbChangeStreamMessageProducerSpec collection(String collection) { - this.target.setCollection(collection); - return this; - } - - /** - * Configure a {@link ChangeStreamOptions}. - * @param options the {@link ChangeStreamOptions} to use. - * @return the spec. - */ - public MongoDbChangeStreamMessageProducerSpec options(ChangeStreamOptions options) { - this.target.setOptions(options); - return this; - } - - /** - * Configure a flag to extract body from a change event or use event as a payload. - * @param extractBody to extract body or not. - * @return the spec. - */ - public MongoDbChangeStreamMessageProducerSpec extractBody(boolean extractBody) { - this.target.setExtractBody(extractBody); - return this; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbMessageSourceSpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbMessageSourceSpec.java deleted file mode 100644 index 3649712f380..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbMessageSourceSpec.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.expression.Expression; -import org.springframework.integration.mongodb.inbound.MongoDbMessageSource; - -/** - * A {@link AbstractMongoDbMessageSourceSpec} implementation for a {@link MongoDbMessageSource}. - * - * @author Artem Bilan - * - * @since 5.5 - */ -public class MongoDbMessageSourceSpec - extends AbstractMongoDbMessageSourceSpec { - - protected MongoDbMessageSourceSpec(MongoDatabaseFactory mongoDatabaseFactory, Expression queryExpression) { - this.target = new MongoDbMessageSource(mongoDatabaseFactory, queryExpression); - } - - protected MongoDbMessageSourceSpec(MongoOperations mongoTemplate, Expression queryExpression) { - this.target = new MongoDbMessageSource(mongoTemplate, queryExpression); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java deleted file mode 100644 index e20a86dd576..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import java.util.function.Function; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.dsl.MessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.mongodb.outbound.MessageCollectionCallback; -import org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway; -import org.springframework.messaging.Message; - -/** - * A {@link MessageHandlerSpec} extension for the MongoDb Outbound endpoint {@link MongoDbOutboundGateway}. - * - * @author Xavier Padro - * @author Artem Bilan - * - * @since 5.0 - */ -public class MongoDbOutboundGatewaySpec - extends MessageHandlerSpec { - - protected MongoDbOutboundGatewaySpec(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) { - this.target = new MongoDbOutboundGateway(mongoDbFactory, mongoConverter); - this.target.setRequiresReply(true); - } - - protected MongoDbOutboundGatewaySpec(MongoOperations mongoTemplate) { - this.target = new MongoDbOutboundGateway(mongoTemplate); - this.target.setRequiresReply(true); - } - - /** - * This parameter indicates that only one result object will be returned from the database - * by using a {@code findOne} query. - * If set to {@code false} (default), the complete result list is returned as the payload. - * @param expectSingleResult the {@code boolean} flag to indicate if a single result is returned or not. - * @return the spec - */ - public MongoDbOutboundGatewaySpec expectSingleResult(boolean expectSingleResult) { - this.target.setExpectSingleResult(expectSingleResult); - return this; - } - - /** - * A {@code String} representation of a MongoDb {@link Query} (e.g., query("{'name' : 'Bob'}")). - * Please refer to MongoDb documentation for more query samples - * see MongoDB Docs - * This property is mutually exclusive with 'queryExpression' property. - * @param query the MongoDb {@link Query} string representation to use. - * @return the spec - */ - public MongoDbOutboundGatewaySpec query(String query) { - this.target.setQueryExpression(new LiteralExpression(query)); - return this; - } - - /** - * A SpEL expression which should resolve to a {@code String} query (please refer to the 'query' property), - * or to an instance of MongoDb {@link Query} - * (e.q., queryExpression("new BasicQuery('{''address.state'' : ''PA''}')")). - * @param queryExpression the SpEL expression query to use. - * @return the spec - */ - public MongoDbOutboundGatewaySpec queryExpression(String queryExpression) { - this.target.setQueryExpressionString(queryExpression); - return this; - } - - /** - * A {@link Function} which should resolve to a {@link Query} instance. - * @param queryFunction the {@link Function} to use. - * @param

the type of the message payload. - * @return the spec - */ - public

MongoDbOutboundGatewaySpec queryFunction(Function, Query> queryFunction) { - this.target.setQueryExpression(new FunctionExpression<>(queryFunction)); - return this; - } - - /** - * The fully qualified name of the entity class to be passed - * to {@code find(..)} or {@code findOne(..)} method in {@link MongoOperations}. - * If this attribute is not provided the default value is {@link org.bson.Document}. - * @param entityClass the {@link Class} to use. - * @return the spec - */ - public MongoDbOutboundGatewaySpec entityClass(Class entityClass) { - this.target.setEntityClass(entityClass); - return this; - } - - /** - * Identify the name of the MongoDb collection to use. - * This attribute is mutually exclusive with {@link #collectionNameExpression} property. - * @param collectionName the {@link String} specifying the MongoDb collection. - * @return the spec - */ - public MongoDbOutboundGatewaySpec collectionName(String collectionName) { - this.target.setCollectionNameExpression(new LiteralExpression(collectionName)); - return this; - } - - /** - * A SpEL expression which should resolve to a {@link String} value - * identifying the name of the MongoDb collection to use. - * This property is mutually exclusive with {@link #collectionName} property. - * @param collectionNameExpression the {@link String} expression to use. - * @return the spec - */ - public MongoDbOutboundGatewaySpec collectionNameExpression(String collectionNameExpression) { - this.target.setCollectionNameExpressionString(collectionNameExpression); - return this; - } - - /** - * A {@link Function} which should resolve to a {@link String} - * (e.q., {@code collectionNameFunction(Message::getPayload)}). - * @param collectionNameFunction the {@link Function} to use. - * @param

the type of the message payload. - * @return the spec - */ - public

MongoDbOutboundGatewaySpec collectionNameFunction(Function, String> collectionNameFunction) { - this.target.setCollectionNameExpression(new FunctionExpression<>(collectionNameFunction)); - return this; - } - - /** - * Reference to an instance of {@link MessageCollectionCallback} - * which specifies the database operation to execute in the request message context. - * This property is mutually exclusive with {@link #query} and {@link #queryExpression} properties. - * @param collectionCallback the {@link MessageCollectionCallback} instance - * @param

the type of the message payload. - * @return the spec - * @since 5.0.11 - */ - public

MongoDbOutboundGatewaySpec collectionCallback(MessageCollectionCallback

collectionCallback) { - this.target.setMessageCollectionCallback(collectionCallback); - return this; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/ReactiveMongoDbMessageHandlerSpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/ReactiveMongoDbMessageHandlerSpec.java deleted file mode 100644 index bf370638724..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/ReactiveMongoDbMessageHandlerSpec.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import java.util.function.Function; - -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.dsl.ComponentsRegistration; -import org.springframework.integration.dsl.ReactiveMessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.mongodb.outbound.ReactiveMongoDbStoringMessageHandler; -import org.springframework.messaging.Message; - -/** - * A {@link ReactiveMessageHandlerSpec} extension for the Reactive MongoDb Outbound endpoint - * {@link ReactiveMongoDbStoringMessageHandler}. - * - * @author Artem Bilan - * - * @since 5.3 - */ -public class ReactiveMongoDbMessageHandlerSpec - extends ReactiveMessageHandlerSpec - implements ComponentsRegistration { - - protected ReactiveMongoDbMessageHandlerSpec(ReactiveMongoDatabaseFactory mongoDbFactory) { - super(new ReactiveMongoDbStoringMessageHandler(mongoDbFactory)); - } - - protected ReactiveMongoDbMessageHandlerSpec(ReactiveMongoOperations reactiveMongoOperations) { - super(new ReactiveMongoDbStoringMessageHandler(reactiveMongoOperations)); - } - - /** - * Configure a {@link MongoConverter}. - * @param mongoConverter the {@link MongoConverter} to use. - * @return the spec - */ - public ReactiveMongoDbMessageHandlerSpec mongoConverter(MongoConverter mongoConverter) { - this.reactiveMessageHandler.setMongoConverter(mongoConverter); - return this; - } - - /** - * Configure a collection name to store data. - * @param collectionName the explicit collection name to use. - * @return the spec - */ - public ReactiveMongoDbMessageHandlerSpec collectionName(String collectionName) { - return collectionNameExpression(new LiteralExpression(collectionName)); - } - - /** - * Configure a {@link Function} for evaluation a collection against request message. - * @param collectionNameFunction the {@link Function} to determine a collection name at runtime. - * @param

an expected payload type - * @return the spec - */ - public

ReactiveMongoDbMessageHandlerSpec collectionNameFunction( - Function, String> collectionNameFunction) { - - return collectionNameExpression(new FunctionExpression<>(collectionNameFunction)); - } - - /** - * Configure a SpEL expression to evaluate a collection name against a request message. - * @param collectionNameExpression the SpEL expression to use. - * @return the spec - */ - public ReactiveMongoDbMessageHandlerSpec collectionNameExpression(Expression collectionNameExpression) { - this.reactiveMessageHandler.setCollectionNameExpression(collectionNameExpression); - return this; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/ReactiveMongoDbMessageSourceSpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/ReactiveMongoDbMessageSourceSpec.java deleted file mode 100644 index 6249aaf275f..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/ReactiveMongoDbMessageSourceSpec.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.expression.Expression; -import org.springframework.integration.mongodb.inbound.ReactiveMongoDbMessageSource; - -/** - * A {@link AbstractMongoDbMessageSourceSpec} implementation for a {@link ReactiveMongoDbMessageSource}. - * - * @author Artem Bilan - * - * @since 5.3 - */ -public class ReactiveMongoDbMessageSourceSpec - extends AbstractMongoDbMessageSourceSpec { - - protected ReactiveMongoDbMessageSourceSpec(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory, - Expression queryExpression) { - - this.target = new ReactiveMongoDbMessageSource(reactiveMongoDatabaseFactory, queryExpression); - } - - protected ReactiveMongoDbMessageSourceSpec(ReactiveMongoOperations reactiveMongoTemplate, - Expression queryExpression) { - - this.target = new ReactiveMongoDbMessageSource(reactiveMongoTemplate, queryExpression); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java deleted file mode 100644 index 2e4a5fc95fc..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides MongoDB Components support for Java DSL. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.dsl; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/AbstractMongoDbMessageSource.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/AbstractMongoDbMessageSource.java deleted file mode 100644 index 8dc3dc9cab5..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/AbstractMongoDbMessageSource.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import java.util.Collection; -import java.util.Map; - -import com.mongodb.DBObject; -import org.bson.Document; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.data.mapping.IdentifierAccessor; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.BasicUpdate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.data.util.Pair; -import org.springframework.expression.Expression; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardTypeLocator; -import org.springframework.integration.endpoint.AbstractMessageSource; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * An {@link AbstractMessageSource} extension for common MongoDB sources options and support methods. - * - * @param The payload type. - * - * @author Artem Bilan - * @author Ngoc Nhan - * - * @since 5.5 - */ -public abstract class AbstractMongoDbMessageSource extends AbstractMessageSource - implements ApplicationContextAware { - - private static final String ID_FIELD = "_id"; - - protected final Expression queryExpression; // NOSONAR - final - - private Expression collectionNameExpression = new LiteralExpression("data"); - - @SuppressWarnings("NullAway.Init") - private MongoConverter mongoConverter; - - private Class entityClass = DBObject.class; - - private boolean expectSingleResult = false; - - private @Nullable Expression updateExpression; - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - private volatile boolean initialized = false; - - protected AbstractMongoDbMessageSource(Expression queryExpression) { - Assert.notNull(queryExpression, "'queryExpression' must not be null"); - this.queryExpression = queryExpression; - } - - /** - * Set the type of the entityClass that will be passed to the find MongoDb template operation. - * Default is {@link DBObject}. - * @param entityClass The entity class. - */ - public void setEntityClass(Class entityClass) { - Assert.notNull(entityClass, "'entityClass' must not be null"); - this.entityClass = entityClass; - } - - /** - * Manage which find* method to invoke. - * Default is 'false', which means the {@link #receive()} method will use - * the {@code find()} method. If set to 'true', - * {@link #receive()} will use {@code findOne(Query, Class)}, - * and the payload of the returned {@link org.springframework.messaging.Message} - * will be the returned target Object of type - * identified by {@link #entityClass} instead of a List. - * @param expectSingleResult true if a single result is expected. - */ - public void setExpectSingleResult(boolean expectSingleResult) { - this.expectSingleResult = expectSingleResult; - } - - /** - * Set the SpEL {@link Expression} that should resolve to a collection name - * used by the {@link Query}. The resulting collection name will be included - * in the {@link org.springframework.integration.mongodb.support.MongoHeaders#COLLECTION_NAME} header. - * @param collectionNameExpression The collection name expression. - */ - public void setCollectionNameExpression(Expression collectionNameExpression) { - Assert.notNull(collectionNameExpression, "'collectionNameExpression' must not be null"); - this.collectionNameExpression = collectionNameExpression; - } - - /** - * Provide a custom {@link MongoConverter} used to assist in deserialization - * data read from MongoDb. - * @param mongoConverter The mongo converter. - */ - public void setMongoConverter(MongoConverter mongoConverter) { - this.mongoConverter = mongoConverter; - } - - /** - * Specify an optional {@code update} for just polled records from the collection. - * @param updateExpression SpEL expression for an - * {@link org.springframework.data.mongodb.core.query.UpdateDefinition}. - * @since 5.5 - */ - public void setUpdateExpression(Expression updateExpression) { - this.updateExpression = updateExpression; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - public Expression getCollectionNameExpression() { - return this.collectionNameExpression; - } - - public MongoConverter getMongoConverter() { - return this.mongoConverter; - } - - public Class getEntityClass() { - return this.entityClass; - } - - public boolean isExpectSingleResult() { - return this.expectSingleResult; - } - - public @Nullable Expression getUpdateExpression() { - return this.updateExpression; - } - - public ApplicationContext getApplicationContext() { - return this.applicationContext; - } - - protected void setInitialized(boolean initialized) { - this.initialized = initialized; - } - - protected boolean isInitialized() { - return this.initialized; - } - - @Override - protected void onInit() { - super.onInit(); - TypeLocator typeLocator = getEvaluationContext().getTypeLocator(); - if (typeLocator instanceof StandardTypeLocator standardTypeLocator) { - //Register MongoDB query API package so FQCN can be avoided in query-expression. - standardTypeLocator.registerImport("org.springframework.data.mongodb.core.query"); - } - } - - protected Query evaluateQueryExpression() { - Object value = this.queryExpression.getValue(getEvaluationContext()); - Assert.notNull(value, "'queryExpression' must not evaluate to null"); - Query query = null; - if (value instanceof String string) { - return new BasicQuery(string); - } - else if (value instanceof Query castQuery) { - return castQuery; - } - else { - throw new IllegalStateException("'queryExpression' must evaluate to String " + - "or org.springframework.data.mongodb.core.query.Query, but not: " + value); - } - } - - protected String evaluateCollectionNameExpression() { - String collectionName = getCollectionNameExpression().getValue(getEvaluationContext(), String.class); - Assert.notNull(collectionName, "'collectionNameExpression' must not evaluate to null"); - return collectionName; - } - - /* - * Inspired by {@code org.springframework.data.mongodb.core.EntityOperations#getByIdInQuery} - */ - protected Query getByIdInQuery(Collection entities) { - MultiValueMap byIds = new LinkedMultiValueMap<>(); - - entities.stream() - .map(this::idForEntity) - .forEach(it -> byIds.add(it.getFirst(), it.getSecond())); - - Criteria[] criterias = byIds.entrySet().stream() - .map(it -> Criteria.where(it.getKey()).in(it.getValue())) - .toArray(Criteria[]::new); - - return new Query(criterias.length == 1 ? criterias[0] : new Criteria().orOperator(criterias)); - } - - protected Pair idForEntity(Object entity) { - if (entity instanceof String) { - return idFieldFromMap(Document.parse(entity.toString())); - } - if (entity instanceof Map asMap) { - return idFieldFromMap(asMap); - } - - var mappingContext = this.mongoConverter.getMappingContext(); - - MongoPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass()); - String idField = persistentEntity.getRequiredIdProperty().getFieldName(); - IdentifierAccessor idAccessor = persistentEntity.getIdentifierAccessor(entity); - return Pair.of(idField, idAccessor.getRequiredIdentifier()); - } - - @Nullable - protected Update evaluateUpdateExpression() { - if (this.updateExpression != null) { - Object value = this.updateExpression.getValue(getEvaluationContext()); - Assert.notNull(value, "'updateExpression' must not evaluate to null"); - if (value instanceof String string) { - return new BasicUpdate(string); - } - else if (value instanceof Update castUpdate) { - return castUpdate; - } - else { - throw new IllegalStateException("'updateExpression' must evaluate to String " + - "or org.springframework.data.mongodb.core.query.Update"); - } - } - return null; - } - - @SuppressWarnings("NullAway") - private static Pair idFieldFromMap(Map map) { - return Pair.of(ID_FIELD, map.get(ID_FIELD)); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbChangeStreamMessageProducer.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbChangeStreamMessageProducer.java deleted file mode 100644 index dceedce8a22..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbChangeStreamMessageProducer.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import org.bson.Document; -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Flux; - -import org.springframework.data.mongodb.core.ChangeStreamOptions; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.mongodb.support.MongoHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * A {@link MessageProducerSupport} for MongoDB Change Stream implementation. - * The functionality is based on the - * {@link ReactiveMongoOperations#changeStream(String, ChangeStreamOptions, Class)} - * and {@link MessageProducerSupport#subscribeToPublisher(org.reactivestreams.Publisher)} consumption. - * - * @author Artem Bilan - * - * @since 5.3 - */ -public class MongoDbChangeStreamMessageProducer extends MessageProducerSupport { - - private final ReactiveMongoOperations mongoOperations; - - private Class domainType = Document.class; - - private @Nullable String collection; - - private ChangeStreamOptions options = ChangeStreamOptions.empty(); - - private boolean extractBody = true; - - /** - * Create an instance based on the provided {@link ReactiveMongoOperations}. - * @param mongoOperations the {@link ReactiveMongoOperations} to use. - * @see ReactiveMongoOperations#changeStream(String, ChangeStreamOptions, Class) - */ - public MongoDbChangeStreamMessageProducer(ReactiveMongoOperations mongoOperations) { - Assert.notNull(mongoOperations, "'mongoOperations' must not be null"); - this.mongoOperations = mongoOperations; - } - - /** - * Specify an object type to convert an event body to. - * Defaults to {@link Document} class. - * @param domainType the class for event body conversion. - * @see ReactiveMongoOperations#changeStream(String, ChangeStreamOptions, Class) - */ - public void setDomainType(Class domainType) { - Assert.notNull(domainType, "'domainType' must not be null"); - this.domainType = domainType; - } - - /** - * Specify a collection name to track change events from. - * By default tracks all the collection in the {@link #mongoOperations} configured database. - * @param collection a collection to use. - * @see ReactiveMongoOperations#changeStream(String, ChangeStreamOptions, Class) - */ - public void setCollection(String collection) { - this.collection = collection; - } - - /** - * Specify a {@link ChangeStreamOptions}. - * @param options the {@link ChangeStreamOptions} to use. - * @see ReactiveMongoOperations#changeStream(String, ChangeStreamOptions, Class) - */ - public void setOptions(ChangeStreamOptions options) { - Assert.notNull(options, "'options' must not be null"); - this.options = options; - } - - /** - * Configure this channel adapter to build a {@link Message} to produce - * with a payload based on a {@link org.springframework.data.mongodb.core.ChangeStreamEvent#getBody()} (by default) - * or use a whole {@link org.springframework.data.mongodb.core.ChangeStreamEvent} as a payload. - * @param extractBody to extract {@link org.springframework.data.mongodb.core.ChangeStreamEvent#getBody()} or not. - */ - public void setExtractBody(boolean extractBody) { - this.extractBody = extractBody; - } - - @Override - public String getComponentType() { - return "mongo:change-stream-inbound-channel-adapter"; - } - - @Override - protected void doStart() { - Flux> changeStreamFlux = - this.mongoOperations.changeStream(this.collection, this.options, this.domainType) - .map(event -> - MessageBuilder - .withPayload( - !this.extractBody || event.getBody() == null - ? event - : event.getBody()) - .setHeader(MongoHeaders.COLLECTION_NAME, event.getCollectionName()) - .setHeader(MongoHeaders.CHANGE_STREAM_OPERATION_TYPE, event.getOperationType()) - .setHeader(MongoHeaders.CHANGE_STREAM_TIMESTAMP, event.getTimestamp()) - .setHeader(MongoHeaders.CHANGE_STREAM_RESUME_TOKEN, event.getResumeToken()) - .build()); - - subscribeToPublisher(changeStreamFlux); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java deleted file mode 100644 index fe48044571c..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import java.util.Collection; -import java.util.List; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.data.util.Pair; -import org.springframework.expression.Expression; -import org.springframework.integration.mongodb.support.MongoHeaders; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.transaction.IntegrationResourceHolder; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * An instance of {@link org.springframework.integration.core.MessageSource} which returns - * a {@link org.springframework.messaging.Message} with a payload which is the result of - * execution of a {@link Query}. When expectSingleResult is false (default), the MongoDb - * {@link Query} is executed using {@link MongoOperations#find(Query, Class)} method which - * returns a {@link List}. The returned {@link List} will be used as the payload of the - * {@link org.springframework.messaging.Message} returned by the {{@link #receive()} - * method. An empty {@link List} is treated as null, thus resulting in no - * {@link org.springframework.messaging.Message} returned by the {{@link #receive()} - * method. - *

- * When expectSingleResult is true, the {@link MongoOperations#findOne(Query, Class)} is - * used instead, and the message payload will be the single object returned from the - * query. - * - * @author Amol Nayak - * @author Oleg Zhurakousky - * @author Yaron Yamin - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.2 - */ -public class MongoDbMessageSource extends AbstractMongoDbMessageSource { - - private final @Nullable MongoDatabaseFactory mongoDbFactory; - - @SuppressWarnings("NullAway.Init") - private MongoOperations mongoTemplate; - - /** - * Create an instance with the provided {@link MongoDatabaseFactory} and SpEL expression - * which should resolve to a MongoDb 'query' string. - * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * @param mongoDbFactory The mongodb factory. - * @param queryExpression The query expression. - */ - public MongoDbMessageSource(MongoDatabaseFactory mongoDbFactory, Expression queryExpression) { - super(queryExpression); - Assert.notNull(mongoDbFactory, "'mongoDbFactory' must not be null"); - this.mongoDbFactory = mongoDbFactory; - } - - /** - * Create an instance with the provided {@link MongoOperations} and SpEL expression - * which should resolve to a Mongo 'query' string. - * It assumes that the {@link MongoOperations} is fully initialized and ready to be used. - * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * @param mongoTemplate The mongo template. - * @param queryExpression The query expression. - */ - public MongoDbMessageSource(MongoOperations mongoTemplate, Expression queryExpression) { - super(queryExpression); - Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null"); - this.mongoDbFactory = null; - this.mongoTemplate = mongoTemplate; - } - - @Override - public String getComponentType() { - return "mongo:inbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.mongoTemplate == null) { - Assert.state(this.mongoDbFactory != null, "'mongoDbFactory' must not be null if 'mongoTemplate' is null."); - MongoTemplate template = new MongoTemplate(this.mongoDbFactory, getMongoConverter()); - template.setApplicationContext(getApplicationContext()); - this.mongoTemplate = template; - } - setMongoConverter(this.mongoTemplate.getConverter()); - setInitialized(true); - } - - /** - * Will execute a {@link Query} returning its results as the Message payload. - * The payload can be either {@link List} of elements of objects of type - * identified by {@link #getEntityClass()}, or a single element of type identified by {@link #getEntityClass()} - * based on the value of {@link #isExpectSingleResult()} attribute which defaults to 'false' resulting - * {@link org.springframework.messaging.Message} with payload of type - * {@link List}. The collection name used in the - * query will be provided in the {@link MongoHeaders#COLLECTION_NAME} header. - */ - @Override - protected @Nullable Object doReceive() { - Assert.isTrue(isInitialized(), "This class is not yet initialized. Invoke its afterPropertiesSet() method"); - AbstractIntegrationMessageBuilder messageBuilder = null; - - Query query = evaluateQueryExpression(); - - String collectionName = evaluateCollectionNameExpression(); - - Object result = null; - if (isExpectSingleResult()) { - result = this.mongoTemplate.findOne(query, getEntityClass(), collectionName); - } - else { - List results = this.mongoTemplate.find(query, getEntityClass(), collectionName); - if (!CollectionUtils.isEmpty(results)) { - result = results; - } - } - if (result != null) { - updateIfAny(result, collectionName); - messageBuilder = - getMessageBuilderFactory() - .withPayload(result) - .setHeader(MongoHeaders.COLLECTION_NAME, collectionName); - } - - Object holder = TransactionSynchronizationManager.getResource(this); - if (holder != null) { - Assert.isInstanceOf(IntegrationResourceHolder.class, holder); - ((IntegrationResourceHolder) holder).addAttribute("mongoTemplate", this.mongoTemplate); - } - - return messageBuilder; - } - - private void updateIfAny(Object result, String collectionName) { - Update update = evaluateUpdateExpression(); - if (update != null) { - if (result instanceof List) { - this.mongoTemplate.updateMulti(getByIdInQuery((Collection) result), update, collectionName); - } - else { - Pair idPair = idForEntity(result); - Query query = new Query(Criteria.where(idPair.getFirst()).is(idPair.getSecond())); - this.mongoTemplate.updateFirst(query, update, collectionName); - } - } - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/ReactiveMongoDbMessageSource.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/ReactiveMongoDbMessageSource.java deleted file mode 100644 index 30ff55e0319..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/ReactiveMongoDbMessageSource.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.data.util.Pair; -import org.springframework.expression.Expression; -import org.springframework.integration.mongodb.support.MongoHeaders; -import org.springframework.util.Assert; - -/** - * An instance of {@link org.springframework.integration.core.MessageSource} which returns - * a {@link org.springframework.messaging.Message} with a payload which is the result of - * execution of a {@link Query}. When {@code expectSingleResult} is false (default), the MongoDb - * {@link Query} is executed using {@link ReactiveMongoOperations#find(Query, Class)} method which - * returns a {@link reactor.core.publisher.Flux}. - * The returned {@link reactor.core.publisher.Flux} will be used as the payload of the - * {@link org.springframework.messaging.Message} returned by the {@link #receive()} - * method. - *

- * When {@code expectSingleResult} is true, the {@link ReactiveMongoOperations#findOne(Query, Class)} is - * used instead, and the message payload will be a {@link reactor.core.publisher.Mono} - * for the single object returned from the query. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 5.3 - */ -public class ReactiveMongoDbMessageSource extends AbstractMongoDbMessageSource> { - - private final @Nullable ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory; - - @SuppressWarnings("NullAway.Init") - private ReactiveMongoOperations reactiveMongoTemplate; - - /** - * Create an instance with the provided {@link ReactiveMongoDatabaseFactory} and SpEL expression - * which should resolve to a MongoDb 'query' string. - * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * @param reactiveMongoDatabaseFactory The reactiveMongoDatabaseFactory factory. - * @param queryExpression The query expression. - */ - public ReactiveMongoDbMessageSource(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory, - Expression queryExpression) { - - super(queryExpression); - Assert.notNull(reactiveMongoDatabaseFactory, "'reactiveMongoDatabaseFactory' must not be null"); - this.reactiveMongoDatabaseFactory = reactiveMongoDatabaseFactory; - } - - /** - * Create an instance with the provided {@link ReactiveMongoOperations} and SpEL expression - * which should resolve to a Mongo 'query' string. - * It assumes that the {@link ReactiveMongoOperations} is fully initialized and ready to be used. - * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * @param reactiveMongoTemplate The reactive Mongo template. - * @param queryExpression The query expression. - */ - public ReactiveMongoDbMessageSource(ReactiveMongoOperations reactiveMongoTemplate, Expression queryExpression) { - super(queryExpression); - Assert.notNull(reactiveMongoTemplate, "'reactiveMongoTemplate' must not be null"); - this.reactiveMongoDatabaseFactory = null; - this.reactiveMongoTemplate = reactiveMongoTemplate; - } - - @Override - public String getComponentType() { - return "mongo:reactive-inbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.reactiveMongoTemplate == null) { - Assert.state(this.reactiveMongoDatabaseFactory != null, - "'reactiveMongoDatabaseFactory' must not be null if 'reactiveMongoTemplate' is null."); - ReactiveMongoTemplate template = - new ReactiveMongoTemplate(this.reactiveMongoDatabaseFactory, getMongoConverter()); - template.setApplicationContext(getApplicationContext()); - this.reactiveMongoTemplate = template; - } - setMongoConverter(this.reactiveMongoTemplate.getConverter()); - setInitialized(true); - } - - /** - * Execute a {@link Query} returning its results as the Message payload. - * The payload can be either {@link reactor.core.publisher.Flux} or - * {@link reactor.core.publisher.Mono} of objects of type identified by {@link #getEntityClass()}, - * or a single element of type identified by {@link #getEntityClass()} - * based on the value of {@link #isExpectSingleResult()} attribute which defaults to 'false' resulting - * {@link org.springframework.messaging.Message} with payload of type - * {@link reactor.core.publisher.Flux}. The collection name used in the - * query will be provided in the {@link MongoHeaders#COLLECTION_NAME} header. - */ - @Override - public Object doReceive() { - Assert.isTrue(isInitialized(), "This class is not yet initialized. Invoke its afterPropertiesSet() method"); - - Query query = evaluateQueryExpression(); - - String collectionName = evaluateCollectionNameExpression(); - - Publisher result; - if (isExpectSingleResult()) { - result = this.reactiveMongoTemplate.findOne(query, getEntityClass(), collectionName); - } - else { - result = this.reactiveMongoTemplate.find(query, getEntityClass(), collectionName); - } - - result = updateIfAny(result, collectionName); - - return getMessageBuilderFactory() - .withPayload(result) - .setHeader(MongoHeaders.COLLECTION_NAME, collectionName); - } - - private Publisher updateIfAny(Publisher result, String collectionName) { - Update update = evaluateUpdateExpression(); - if (update != null) { - if (result instanceof Mono) { - return updateSingle((Mono) result, update, collectionName); - } - else { - return updateMulti((Flux) result, update, collectionName); - } - } - else { - return result; - } - } - - private Publisher updateSingle(Mono result, Update update, String collectionName) { - return result.flatMap((entity) -> { - Pair idPair = idForEntity(entity); - Query query = new Query(Criteria.where(idPair.getFirst()).is(idPair.getSecond())); - return this.reactiveMongoTemplate.updateFirst(query, update, collectionName) - .thenReturn(entity); - }); - } - - private Publisher updateMulti(Flux result, Update update, String collectionName) { - return result.collectList() - .flatMapMany((entities) -> - this.reactiveMongoTemplate.updateMulti(getByIdInQuery(entities), update, collectionName) - .thenMany(Flux.fromIterable(entities)) - ); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/package-info.java deleted file mode 100644 index f0dd38844bd..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to the Mongo inbound channel adapters - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.inbound; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/metadata/MongoDbMetadataStore.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/metadata/MongoDbMetadataStore.java deleted file mode 100644 index 121d5d5793d..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/metadata/MongoDbMetadataStore.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.metadata; - -import java.util.HashMap; -import java.util.Map; - -import org.bson.Document; -import org.jspecify.annotations.Nullable; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.FindAndModifyOptions; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.integration.metadata.ConcurrentMetadataStore; -import org.springframework.util.Assert; - -/** - * MongoDbMetadataStore implementation of {@link ConcurrentMetadataStore}. - * Use this {@link org.springframework.integration.metadata.MetadataStore} to - * achieve meta-data persistence shared across application instances and - * restarts. - * - * @author Senthil Arumugam, Samiraj Panneer Selvam - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.2 - * - */ -public class MongoDbMetadataStore implements ConcurrentMetadataStore { - - private static final String KEY_MUST_NOT_BE_EMPTY = "'key' must not be empty."; - - private static final String DEFAULT_COLLECTION_NAME = "metadataStore"; - - private static final String ID_FIELD = "_id"; - - private static final String VALUE = "value"; - - private final MongoTemplate template; - - private final String collectionName; - - /** - * Configure the MongoDbMetadataStore by provided {@link MongoDatabaseFactory} and - * default collection name - {@link #DEFAULT_COLLECTION_NAME}. - * @param factory the mongodb factory - */ - public MongoDbMetadataStore(MongoDatabaseFactory factory) { - this(factory, DEFAULT_COLLECTION_NAME); - } - - /** - * Configure the MongoDbMetadataStore by provided {@link MongoDatabaseFactory} and - * collection name. - * @param factory the mongodb factory - * @param collectionName the collection name where it persists the data - */ - public MongoDbMetadataStore(MongoDatabaseFactory factory, String collectionName) { - this(new MongoTemplate(factory), collectionName); - } - - /** - * Configure the MongoDbMetadataStore by provided {@link MongoTemplate} and - * default collection name - {@link #DEFAULT_COLLECTION_NAME}. - * @param template the mongodb template - */ - public MongoDbMetadataStore(MongoTemplate template) { - this(template, DEFAULT_COLLECTION_NAME); - } - - /** - * Configure the MongoDbMetadataStore by provided {@link MongoTemplate} and collection name. - * @param template the mongodb template - * @param collectionName the collection name where it persists the data - */ - public MongoDbMetadataStore(MongoTemplate template, String collectionName) { - Assert.notNull(template, "'template' must not be null."); - Assert.hasText(collectionName, "'collectionName' must not be empty."); - this.template = template; - this.collectionName = collectionName; - } - - /** - * Store a metadata {@code value} under provided {@code key} to the configured - * {@link #collectionName}. - *

- * If a document does not exist with the specified {@code key}, the method performs an {@code insert}. - * If a document exists with the specified {@code key}, the method performs an {@code update}. - * @param key the metadata entry key - * @param value the metadata entry value - * @see MongoTemplate#execute(String, org.springframework.data.mongodb.core.CollectionCallback) - */ - @Override - public void put(String key, String value) { - Assert.hasText(key, KEY_MUST_NOT_BE_EMPTY); - Assert.hasText(value, "'value' must not be empty."); - final Map entry = new HashMap<>(); - entry.put(ID_FIELD, key); - entry.put(VALUE, value); - this.template.save(new Document(entry), this.collectionName); - } - - /** - * Get the {@code value} for the provided {@code key} performing {@code findOne} MongoDB operation. - * @param key the metadata entry key - * @return the metadata entry value or null if doesn't exist. - * @see MongoTemplate#findOne(Query, Class, String) - */ - @Override - public @Nullable String get(String key) { - Assert.hasText(key, KEY_MUST_NOT_BE_EMPTY); - Query query = new Query(Criteria.where(ID_FIELD).is(key)); - query.fields().exclude(ID_FIELD); - @SuppressWarnings("unchecked") - Map result = this.template.findOne(query, Map.class, this.collectionName); - return result == null ? null : result.get(VALUE); - - } - - /** - * Remove the metadata entry for the provided {@code key} and return its {@code value}, if any, - * using {@code findAndRemove} MongoDB operation. - * @param key the metadata entry key - * @return the metadata entry value or null if doesn't exist. - * @see MongoTemplate#findAndRemove(Query, Class, String) - */ - @Override - public @Nullable String remove(String key) { - Assert.hasText(key, KEY_MUST_NOT_BE_EMPTY); - Query query = new Query(Criteria.where(ID_FIELD).is(key)); - query.fields().exclude(ID_FIELD); - @SuppressWarnings("unchecked") - Map result = this.template.findAndRemove(query, Map.class, this.collectionName); - return result == null ? null : result.get(VALUE); - } - - /** - * If the specified key is not already associated with a value, associate it with the given value. - * This is equivalent to - *

 {@code
-	 * if (!map.containsKey(key))
-	 *   return map.put(key, value);
-	 * else
-	 *   return map.get(key);
-	 * }
- * except that the action is performed atomically. - * @param key the metadata entry key - * @param value the metadata entry value to store - * @return null if successful, the old value otherwise. - * @see java.util.concurrent.ConcurrentMap#putIfAbsent(Object, Object) - */ - @Override - public @Nullable String putIfAbsent(String key, String value) { - Assert.hasText(key, KEY_MUST_NOT_BE_EMPTY); - Assert.hasText(value, "'value' must not be empty."); - - Query query = new Query(Criteria.where(ID_FIELD).is(key)); - query.fields().exclude(ID_FIELD); - @SuppressWarnings("unchecked") - Map result = this.template.findAndModify(query, new Update().setOnInsert(VALUE, value), - new FindAndModifyOptions().upsert(true), Map.class, this.collectionName); - return result == null ? null : result.get(VALUE); - } - - /** - * Replace an existing metadata entry {@code value} with a new one. Otherwise does nothing. - * Performs {@code updateFirst} if a document for the provided {@code key} and {@code oldValue} - * exists in the {@link #collectionName}. - * @param key the metadata entry key - * @param oldValue the metadata entry old value to replace - * @param newValue the metadata entry new value to put - * @return {@code true} if replace was successful, {@code false} otherwise. - * @see MongoTemplate#updateFirst(Query, org.springframework.data.mongodb.core.query.UpdateDefinition, String) - */ - @Override - public boolean replace(String key, String oldValue, String newValue) { - Assert.hasText(key, KEY_MUST_NOT_BE_EMPTY); - Assert.hasText(oldValue, "'oldValue' must not be empty."); - Assert.hasText(newValue, "'newValue' must not be empty."); - Query query = new Query(Criteria.where(ID_FIELD).is(key).and(VALUE).is(oldValue)); - return this.template.updateFirst(query, Update.update(VALUE, newValue), this.collectionName) - .getModifiedCount() > 0; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/metadata/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/metadata/package-info.java deleted file mode 100644 index 43585b3ef1b..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/metadata/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Contains mongodb metadata store related classes - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.metadata; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MessageCollectionCallback.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MessageCollectionCallback.java deleted file mode 100644 index b6e10dabce7..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MessageCollectionCallback.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import com.mongodb.MongoException; -import com.mongodb.client.MongoCollection; -import org.bson.Document; -import org.jspecify.annotations.Nullable; - -import org.springframework.dao.DataAccessException; -import org.springframework.data.mongodb.core.CollectionCallback; -import org.springframework.messaging.Message; - -/** - * The callback to be used with the {@link MongoDbOutboundGateway} - * as an alternative to other query options on the gateway. - *

- * Plays the same role as standard {@link CollectionCallback}, - * but with {@code Message requestMessage} context during {@code handleMessage()} - * process in the {@link MongoDbOutboundGateway}. - * - * @param the expected item type. - * - * @author Artem Bilan - * - * @since 5.0.11 - * - * @see CollectionCallback - */ -@FunctionalInterface -public interface MessageCollectionCallback extends CollectionCallback { - - /** - * Perform a Mongo operation in the collection using request message as a context. - * @param collection never {@literal null}. - * @param requestMessage the request message ot use for operations - * @return can be {@literal null}. - * @throws MongoException the MongoDB-specific exception - * @throws DataAccessException the data access exception - */ - @Nullable - T doInCollection(MongoCollection collection, Message requestMessage) - throws MongoException, DataAccessException; - - @Override - default T doInCollection(MongoCollection collection) throws MongoException, DataAccessException { - throw new UnsupportedOperationException("The 'doInCollection(MongoCollection, Message)' " + - "must be implemented instead."); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java deleted file mode 100644 index 14b3e62c06e..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import org.bson.Document; -import org.jspecify.annotations.Nullable; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.spel.support.StandardTypeLocator; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Makes outbound operations to query a MongoDb database using a {@link MongoOperations}. - * - * @author Xavier Padro - * @author Artem Bilan - * - * @since 5.0 - */ -public class MongoDbOutboundGateway extends AbstractReplyProducingMessageHandler { - - private @Nullable MongoDatabaseFactory mongoDbFactory; - - private @Nullable MongoConverter mongoConverter; - - @SuppressWarnings("NullAway.Init") - private MongoOperations mongoTemplate; - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - private @Nullable Expression queryExpression; - - private @Nullable MessageCollectionCallback collectionCallback; - - private boolean expectSingleResult = false; - - private Class entityClass = Document.class; - - @SuppressWarnings("NullAway.Init") - private Expression collectionNameExpression; - - public MongoDbOutboundGateway(MongoDatabaseFactory mongoDbFactory) { - this(mongoDbFactory, new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), - new MongoMappingContext())); - } - - public MongoDbOutboundGateway(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) { - Assert.notNull(mongoDbFactory, "mongoDatabaseFactory must not be null."); - Assert.notNull(mongoConverter, "mongoConverter must not be null."); - this.mongoDbFactory = mongoDbFactory; - this.mongoConverter = mongoConverter; - } - - public MongoDbOutboundGateway(MongoOperations mongoTemplate) { - Assert.notNull(mongoTemplate, "mongoTemplate must not be null."); - this.mongoTemplate = mongoTemplate; - } - - public void setQueryExpression(Expression queryExpression) { - Assert.notNull(queryExpression, "queryExpression must not be null."); - this.queryExpression = queryExpression; - } - - public void setQueryExpressionString(String queryExpressionString) { - Assert.notNull(queryExpressionString, "queryExpressionString must not be null."); - this.queryExpression = EXPRESSION_PARSER.parseExpression(queryExpressionString); - } - - /** - * Specify a {@link MessageCollectionCallback} to perform against MongoDB collection - * in the request message context. - * @param collectionCallback the callback to perform against MongoDB collection. - * @since 5.0.11 - */ - public void setMessageCollectionCallback(MessageCollectionCallback collectionCallback) { - Assert.notNull(collectionCallback, "'collectionCallback' must not be null."); - this.collectionCallback = collectionCallback; - } - - public void setExpectSingleResult(boolean expectSingleResult) { - this.expectSingleResult = expectSingleResult; - } - - public void setEntityClass(Class entityClass) { - Assert.notNull(entityClass, "entityClass must not be null."); - this.entityClass = entityClass; - } - - public void setCollectionNameExpression(Expression collectionNameExpression) { - Assert.notNull(collectionNameExpression, "collectionNameExpression must not be null."); - this.collectionNameExpression = collectionNameExpression; - } - - public void setCollectionNameExpressionString(String collectionNameExpressionString) { - Assert.notNull(collectionNameExpressionString, "collectionNameExpressionString must not be null."); - this.collectionNameExpression = EXPRESSION_PARSER.parseExpression(collectionNameExpressionString); - } - - public void setMongoConverter(MongoConverter mongoConverter) { - Assert.notNull(mongoConverter, "mongoConverter cannot be null"); - Assert.isNull(this.mongoTemplate, - "'mongoConverter' can not be set when instance was constructed with MongoTemplate"); - this.mongoConverter = mongoConverter; - } - - @Override - protected void doInit() { - Assert.state(this.queryExpression != null || this.collectionCallback != null, - "no query or collectionCallback is specified"); - Assert.state(this.collectionNameExpression != null, "no collection name specified"); - if (this.queryExpression != null && this.collectionCallback != null) { - throw new IllegalStateException("query and collectionCallback are mutually exclusive"); - } - - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); - - TypeLocator typeLocator = this.evaluationContext.getTypeLocator(); - if (typeLocator instanceof StandardTypeLocator) { - ((StandardTypeLocator) typeLocator).registerImport(Query.class.getPackage().getName()); - } - - if (this.mongoTemplate == null) { - Assert.state(this.mongoDbFactory != null, "'mongoDbFactory' must not be null if 'mongoTemplate' is null."); - this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mongoConverter); - } - } - - @Override - protected @Nullable Object handleRequestMessage(Message requestMessage) { - String collectionName = - this.collectionNameExpression.getValue(this.evaluationContext, requestMessage, String.class); - Assert.notNull(collectionName, "'collectionNameExpression' cannot evaluate to null"); - - var collectionCallbackToUse = this.collectionCallback; - if (collectionCallbackToUse != null) { - return this.mongoTemplate.execute(collectionName, - collection -> collectionCallbackToUse.doInCollection(collection, requestMessage)); - } - else { - Query query = buildQuery(requestMessage); - - if (this.expectSingleResult) { - return this.mongoTemplate.findOne(query, this.entityClass, collectionName); - } - else { - return this.mongoTemplate.find(query, this.entityClass, collectionName); - } - } - } - - private Query buildQuery(Message requestMessage) { - Query query; - Object expressionValue = - getRequiredQueryExpression() - .getValue(this.evaluationContext, requestMessage, Object.class); - - if (expressionValue instanceof String) { - query = new BasicQuery((String) expressionValue); - } - else if (expressionValue instanceof Query) { - query = ((Query) expressionValue); - } - else { - throw new IllegalStateException("'queryExpression' must evaluate to " + - "String or org.springframework.data.mongodb.core.query.Query"); - } - - return query; - } - - @SuppressWarnings("NullAway") // The method is called from the place where we sure that it is not null - private Expression getRequiredQueryExpression() { - return this.queryExpression; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbStoringMessageHandler.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbStoringMessageHandler.java deleted file mode 100644 index 09eee09d4a7..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbStoringMessageHandler.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Implementation of {@link org.springframework.messaging.MessageHandler} - * which writes Message payload into a MongoDb collection - * identified by evaluation of the {@link #collectionNameExpression}. - * - * @author Amol Nayak - * @author Oleg Zhurakousky - * @author Gary Russell - * - * @since 2.2 - * - */ -public class MongoDbStoringMessageHandler extends AbstractMessageHandler { - - private final @Nullable MongoDatabaseFactory mongoDbFactory; - - @SuppressWarnings("NullAway.Init") - private MongoOperations mongoTemplate; - - private @Nullable MongoConverter mongoConverter; - - @SuppressWarnings("NullAway.Init") - private StandardEvaluationContext evaluationContext; - - private Expression collectionNameExpression = new LiteralExpression("data"); - - private volatile boolean initialized = false; - - /** - * Will construct this instance using provided {@link MongoDatabaseFactory}. - * @param mongoDbFactory The mongodb factory. - */ - public MongoDbStoringMessageHandler(MongoDatabaseFactory mongoDbFactory) { - Assert.notNull(mongoDbFactory, "'mongoDbFactory' must not be null"); - this.mongoDbFactory = mongoDbFactory; - } - - /** - * Will construct this instance using fully created and initialized instance of - * provided {@link MongoOperations}. - * @param mongoTemplate The MongoOperations implementation. - */ - public MongoDbStoringMessageHandler(MongoOperations mongoTemplate) { - Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null"); - this.mongoTemplate = mongoTemplate; - this.mongoDbFactory = null; - } - - /** - * Allow providing custom {@link MongoConverter} used to assist in serialization - * of data written to MongoDb. Only allowed if this instance was constructed with a - * {@link MongoDatabaseFactory}. - * @param mongoConverter The mongo converter. - */ - public void setMongoConverter(MongoConverter mongoConverter) { - Assert.isNull(this.mongoTemplate, - "'mongoConverter' can not be set when instance was constructed with MongoTemplate"); - this.mongoConverter = mongoConverter; - } - - /** - * Set the SpEL {@link Expression} that should resolve to a collection name - * used by {@link MongoOperations} to store data - * @param collectionNameExpression The collection name expression. - */ - public void setCollectionNameExpression(Expression collectionNameExpression) { - Assert.notNull(collectionNameExpression, "'collectionNameExpression' must not be null"); - this.collectionNameExpression = collectionNameExpression; - } - - @Override - public String getComponentType() { - return "mongo:outbound-channel-adapter"; - } - - @Override - protected void onInit() { - this.evaluationContext = - ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); - if (this.mongoTemplate == null) { - Assert.state(this.mongoDbFactory != null, "'mongoDbFactory' must not be null if 'mongoTemplate' is null."); - this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mongoConverter); - } - this.initialized = true; - } - - @Override - protected void handleMessageInternal(Message message) { - Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method"); - String collectionName = this.collectionNameExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(collectionName, "'collectionNameExpression' must not evaluate to null"); - - Object payload = message.getPayload(); - - this.mongoTemplate.save(payload, collectionName); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandler.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandler.java deleted file mode 100644 index 65fc045ed2e..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandler.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; - -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractReactiveMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Implementation of {@link org.springframework.messaging.ReactiveMessageHandler} which writes - * Message payload into a MongoDb collection, using reactive MongoDb support, The - * collection is identified by evaluation of the {@link #collectionNameExpression}. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 5.3 - */ -public class ReactiveMongoDbStoringMessageHandler extends AbstractReactiveMessageHandler { - - @SuppressWarnings("NullAway.Init") - private ReactiveMongoOperations mongoTemplate; - - private @Nullable ReactiveMongoDatabaseFactory mongoDbFactory; - - private @Nullable MongoConverter mongoConverter; - - @SuppressWarnings("NullAway.Init") - private StandardEvaluationContext evaluationContext; - - private Expression collectionNameExpression = new LiteralExpression("data"); - - private volatile boolean initialized = false; - - /** - * Construct this instance using a provided {@link ReactiveMongoDatabaseFactory}. - * @param mongoDbFactory The reactive mongoDatabase factory. - */ - public ReactiveMongoDbStoringMessageHandler(ReactiveMongoDatabaseFactory mongoDbFactory) { - Assert.notNull(mongoDbFactory, "'mongoDbFactory' must not be null"); - this.mongoDbFactory = mongoDbFactory; - } - - /** - * Construct this instance using a fully created and initialized instance of provided - * {@link ReactiveMongoOperations}. - * @param mongoTemplate The ReactiveMongoOperations implementation. - */ - public ReactiveMongoDbStoringMessageHandler(ReactiveMongoOperations mongoTemplate) { - Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null"); - this.mongoTemplate = mongoTemplate; - } - - /** - * Provide a custom {@link MongoConverter} used to assist in serialization of - * data written to MongoDb. Only allowed if this instance was constructed with a - * {@link ReactiveMongoDatabaseFactory}. - * @param mongoConverter The mongo converter. - */ - public void setMongoConverter(MongoConverter mongoConverter) { - Assert.isNull(this.mongoTemplate, - "'mongoConverter' can not be set when instance was constructed with MongoTemplate"); - this.mongoConverter = mongoConverter; - } - - /** - * Set a SpEL {@link Expression} that should resolve to a collection name used by - * {@link ReactiveMongoOperations} to store data - * @param collectionNameExpression The collection name expression. - */ - public void setCollectionNameExpression(Expression collectionNameExpression) { - Assert.notNull(collectionNameExpression, "'collectionNameExpression' must not be null"); - this.collectionNameExpression = collectionNameExpression; - } - - @Override - public String getComponentType() { - return "mongo:reactive-outbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - if (this.mongoTemplate == null) { - Assert.state(this.mongoDbFactory != null, "'mongoDbFactory' must not be null if 'mongoTemplate' is null."); - - ReactiveMongoTemplate template = new ReactiveMongoTemplate(this.mongoDbFactory, this.mongoConverter); - template.setApplicationContext(getApplicationContext()); - this.mongoTemplate = template; - } - this.initialized = true; - } - - @Override - protected Mono handleMessageInternal(Message message) { - Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method"); - - return evaluateCollectionNameExpression(message) - .flatMap(collection -> this.mongoTemplate.save(message.getPayload(), collection)) - .then(); - } - - private Mono evaluateCollectionNameExpression(Message message) { - return Mono.fromSupplier(() -> { - String collectionName = - this.collectionNameExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(collectionName, "'collectionNameExpression' must not evaluate to null"); - return collectionName; - }); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/package-info.java deleted file mode 100644 index 3352a1a9123..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to the Mongo outbound channel adapters - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.outbound; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/AbstractConfigurableMongoDbMessageStore.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/AbstractConfigurableMongoDbMessageStore.java deleted file mode 100644 index 37c22eabf16..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/AbstractConfigurableMongoDbMessageStore.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.FindAndModifyOptions; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.MongoCustomConversions; -import org.springframework.data.mongodb.core.index.Index; -import org.springframework.data.mongodb.core.index.IndexOperations; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.integration.mongodb.support.BinaryToMessageConverter; -import org.springframework.integration.mongodb.support.MessageToBinaryConverter; -import org.springframework.integration.store.AbstractMessageGroupStore; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageMetadata; -import org.springframework.integration.support.DefaultMessageBuilderFactory; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * The abstract MongoDB {@link AbstractMessageGroupStore} implementation to provide configuration for common options - * for implementations of this class. - * - * @author Artem Bilan - * @author Adama Sorho - * @author Youbin Wu - * - * @since 4.0 - */ - -public abstract class AbstractConfigurableMongoDbMessageStore extends AbstractMessageGroupStore - implements InitializingBean, ApplicationContextAware { - - public static final String SEQUENCE_NAME = "messagesSequence"; - - protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - final - - private static final RuntimeException NOT_IMPLEMENTED = - new UnsupportedOperationException("The operation isn't implemented for this class."); - - protected final String collectionName; // NOSONAR - final - - protected final @Nullable MongoDatabaseFactory mongoDbFactory; - - @SuppressWarnings("NullAway.Init") - private MongoTemplate mongoTemplate; - - private @Nullable MappingMongoConverter mappingMongoConverter; - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - private boolean createIndexes = true; - - public AbstractConfigurableMongoDbMessageStore(MongoTemplate mongoTemplate, String collectionName) { - Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null"); - Assert.hasText(collectionName, "'collectionName' must not be empty"); - this.collectionName = collectionName; - this.mongoTemplate = mongoTemplate; - this.mongoDbFactory = null; - } - - public AbstractConfigurableMongoDbMessageStore(MongoDatabaseFactory mongoDbFactory, String collectionName) { - this(mongoDbFactory, null, collectionName); - } - - public AbstractConfigurableMongoDbMessageStore(MongoDatabaseFactory mongoDbFactory, - @Nullable MappingMongoConverter mappingMongoConverter, String collectionName) { - Assert.notNull(mongoDbFactory, "'mongoDbFactory' must not be null"); - Assert.hasText(collectionName, "'collectionName' must not be empty"); - this.collectionName = collectionName; - this.mongoDbFactory = mongoDbFactory; - this.mappingMongoConverter = mappingMongoConverter; - } - - /** - * Define the option to auto create indexes or not. - * @param createIndexes a boolean. - * @since 6.0.8. - */ - public void setCreateIndexes(boolean createIndexes) { - this.createIndexes = createIndexes; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - protected MongoTemplate getMongoTemplate() { - return this.mongoTemplate; - } - - protected @Nullable MappingMongoConverter getMappingMongoConverter() { - return this.mappingMongoConverter; - } - - protected ApplicationContext getApplicationContext() { - return this.applicationContext; - } - - protected MessageBuilderFactory getMessageBuilderFactory() { - return this.messageBuilderFactory; - } - - @Override - public void afterPropertiesSet() { - if (this.mongoTemplate == null) { - Assert.state(this.mongoDbFactory != null, "'mongoDbFactory' must not be null if 'mongoTemplate' is null."); - if (this.mappingMongoConverter == null) { - this.mappingMongoConverter = new MappingMongoConverter(new DefaultDbRefResolver(this.mongoDbFactory), - new MongoMappingContext()); - this.mappingMongoConverter.setApplicationContext(this.applicationContext); - List customConverters = new ArrayList<>(); - customConverters.add(new MessageToBinaryConverter()); - customConverters.add(new BinaryToMessageConverter()); - this.mappingMongoConverter.setCustomConversions(new MongoCustomConversions(customConverters)); - this.mappingMongoConverter.afterPropertiesSet(); - } - this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mappingMongoConverter); - } - - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.applicationContext); - - if (this.createIndexes) { - createIndexes(); - } - } - - protected void createIndexes() { - IndexOperations indexOperations = this.mongoTemplate.indexOps(this.collectionName); - - indexOperations.createIndex(new Index(MessageDocumentFields.MESSAGE_ID, Sort.Direction.ASC)); - - indexOperations.createIndex( - new Index(MessageDocumentFields.GROUP_ID, Sort.Direction.ASC) - .on(MessageDocumentFields.MESSAGE_ID, Sort.Direction.ASC) - .unique()); - - indexOperations.createIndex( - new Index(MessageDocumentFields.GROUP_ID, Sort.Direction.ASC) - .on(MessageDocumentFields.LAST_MODIFIED_TIME, Sort.Direction.DESC) - .on(MessageDocumentFields.SEQUENCE, Sort.Direction.DESC)); - } - - public @Nullable Message getMessage(UUID id) { - Assert.notNull(id, "'id' must not be null"); - Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).is(id)); - MessageDocument document = this.mongoTemplate.findOne(query, MessageDocument.class, this.collectionName); - return document != null ? document.getMessage() : null; - } - - public @Nullable MessageMetadata getMessageMetadata(UUID id) { - Assert.notNull(id, "'id' must not be null"); - Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).is(id)); - MessageDocument document = this.mongoTemplate.findOne(query, MessageDocument.class, this.collectionName); - if (document != null) { - MessageMetadata messageMetadata = new MessageMetadata(id); - messageMetadata.setTimestamp(document.getCreatedTime()); - return messageMetadata; - } - else { - return null; - } - } - - @Override - protected void doRemoveMessageGroup(Object groupId) { - this.mongoTemplate.remove(groupIdQuery(groupId), this.collectionName); - } - - @Override - public int messageGroupSize(Object groupId) { - long lCount = this.mongoTemplate.count(groupIdQuery(groupId), this.collectionName); - Assert.isTrue(lCount <= Integer.MAX_VALUE, "Message count is out of Integer's range"); - return (int) lCount; - } - - /** - * Perform MongoDB {@code INC} operation for the document, which contains the {@link MessageDocument} - * {@code sequence}, and return the new incremented value for the new {@link MessageDocument}. - * The {@link #SEQUENCE_NAME} document is created on demand. - * @return the next sequence value. - */ - @SuppressWarnings("NullAway") - protected long getNextId() { - Query query = Query.query(Criteria.where("_id").is(SEQUENCE_NAME)); - query.fields().include(MessageDocumentFields.SEQUENCE); - return ((Number) this.mongoTemplate.findAndModify(query, - new Update().inc(MessageDocumentFields.SEQUENCE, 1L), - FindAndModifyOptions.options().returnNew(true).upsert(true), - Map.class, this.collectionName) - .get(MessageDocumentFields.SEQUENCE)) - .longValue(); - } - - protected void addMessageDocument(final MessageDocument document) { - if (document.getGroupCreatedTime() == 0) { - document.setGroupCreatedTime(System.currentTimeMillis()); - } - document.setCreatedTime(System.currentTimeMillis()); - try { - this.mongoTemplate.insert(document, this.collectionName); - } - catch (DataIntegrityViolationException e) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("The Message with id [" + document.getMessageId() + "] already exists.\n" + - "Ignoring INSERT and SELECT existing..."); - } - } - } - - protected static Query groupIdQuery(Object groupId) { - return Query.query(Criteria.where(MessageDocumentFields.GROUP_ID).is(groupId)); - } - - @Override - protected void doRemoveMessagesFromGroup(Object key, Collection> messages) { - throw NOT_IMPLEMENTED; - } - - @Override - protected void doSetGroupCondition(Object groupId, String condition) { - throw NOT_IMPLEMENTED; - } - - @Override - protected void doSetLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) { - throw NOT_IMPLEMENTED; - } - - @Override - public Iterator iterator() { - throw NOT_IMPLEMENTED; - } - - @Override - protected void doCompleteGroup(Object groupId) { - throw NOT_IMPLEMENTED; - } - - @Override - protected void doAddMessagesToGroup(Object groupId, Message... messages) { - throw NOT_IMPLEMENTED; - } - - @Override - public Collection> getMessagesForGroup(Object groupId) { - throw NOT_IMPLEMENTED; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStore.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStore.java deleted file mode 100644 index e20a61acc52..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStore.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.FindAndModifyOptions; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageStore; -import org.springframework.integration.store.SimpleMessageGroup; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * An alternate MongoDB {@link MessageStore} and - * {@link org.springframework.integration.store.MessageGroupStore} which allows the user to - * configure the instance of {@link MongoTemplate}. The mechanism of storing the messages/group of messages - * in the store is and is different from {@link MongoDbMessageStore}. Since the store uses serialization of the - * messages by default, all the headers, and the payload of the Message must implement {@link java.io.Serializable} - * interface - * - * @author Amol Nayak - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * @author Youbin Wu - * - * @since 3.0 - */ -public class ConfigurableMongoDbMessageStore extends AbstractConfigurableMongoDbMessageStore - implements MessageStore { - - public static final String DEFAULT_COLLECTION_NAME = "configurableStoreMessages"; - - public ConfigurableMongoDbMessageStore(MongoTemplate mongoTemplate) { - this(mongoTemplate, DEFAULT_COLLECTION_NAME); - } - - public ConfigurableMongoDbMessageStore(MongoTemplate mongoTemplate, String collectionName) { - super(mongoTemplate, collectionName); - } - - public ConfigurableMongoDbMessageStore(MongoDatabaseFactory mongoDbFactory) { - this(mongoDbFactory, null, DEFAULT_COLLECTION_NAME); - } - - public ConfigurableMongoDbMessageStore(MongoDatabaseFactory mongoDbFactory, - MappingMongoConverter mappingMongoConverter) { - - this(mongoDbFactory, mappingMongoConverter, DEFAULT_COLLECTION_NAME); - } - - public ConfigurableMongoDbMessageStore(MongoDatabaseFactory mongoDbFactory, String collectionName) { - this(mongoDbFactory, null, collectionName); - } - - public ConfigurableMongoDbMessageStore(MongoDatabaseFactory mongoDbFactory, - @Nullable MappingMongoConverter mappingMongoConverter, String collectionName) { - - super(mongoDbFactory, mappingMongoConverter, collectionName); - } - - @Override - public Message addMessage(Message message) { - this.addMessageDocument(new MessageDocument(message)); - return message; - } - - @Override - public @Nullable Message removeMessage(UUID id) { - Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).is(id) - .and(MessageDocumentFields.GROUP_ID).exists(false)); - MessageDocument document = getMongoTemplate().findAndRemove(query, MessageDocument.class, this.collectionName); - return (document != null) ? document.getMessage() : null; - } - - @Override - public long getMessageCount() { - Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).exists(true) - .and(MessageDocumentFields.GROUP_ID).exists(false)); - return getMongoTemplate().getCollection(this.collectionName).countDocuments(query.getQueryObject()); - } - - @Override - public MessageGroup getMessageGroup(Object groupId) { - Query query = groupOrderQuery(groupId); - MessageDocument messageDocument = getMongoTemplate().findOne(query, MessageDocument.class, this.collectionName); - - if (messageDocument != null) { - long createdTime = messageDocument.getGroupCreatedTime(); - long lastModifiedTime = messageDocument.getLastModifiedTime(); - boolean complete = messageDocument.isComplete(); - int lastReleasedSequence = messageDocument.getLastReleasedSequence(); - - MessageGroup messageGroup = getMessageGroupFactory() - .create(this, groupId, createdTime, complete); - messageGroup.setLastModified(lastModifiedTime); - messageGroup.setLastReleasedMessageSequenceNumber(lastReleasedSequence); - messageGroup.setCondition(messageDocument.getCondition()); - return messageGroup; - } - else { - return new SimpleMessageGroup(groupId); - } - } - - @Override - public MessageGroup addMessageToGroup(Object groupId, Message message) { - addMessagesToGroup(groupId, message); - return getMessageGroup(groupId); - } - - @Override - protected void doAddMessagesToGroup(Object groupId, Message... messages) { - Query query = groupOrderQuery(groupId); - MessageDocument messageDocument = getMongoTemplate().findOne(query, MessageDocument.class, this.collectionName); - - long createdTime = System.currentTimeMillis(); - int lastReleasedSequence = 0; - boolean complete = false; - - String condition = null; - - if (messageDocument != null) { - createdTime = messageDocument.getGroupCreatedTime(); - lastReleasedSequence = messageDocument.getLastReleasedSequence(); - complete = messageDocument.isComplete(); - condition = messageDocument.getCondition(); - } - - for (Message message : messages) { - MessageDocument document = new MessageDocument(message); - document.setGroupId(groupId); - document.setComplete(complete); - document.setLastReleasedSequence(lastReleasedSequence); - document.setGroupCreatedTime(createdTime); - document.setLastModifiedTime(messageDocument == null ? createdTime : System.currentTimeMillis()); - document.setSequence(getNextId()); - if (condition != null) { - document.setCondition(condition); - } - addMessageDocument(document); - } - } - - @Override - protected void doRemoveMessagesFromGroup(Object groupId, Collection> messages) { - Collection ids = new ArrayList<>(); - for (Message messageToRemove : messages) { - ids.add(Objects.requireNonNull(messageToRemove.getHeaders().getId())); - if (ids.size() >= getRemoveBatchSize()) { - removeMessages(groupId, ids); - ids.clear(); - } - } - if (!ids.isEmpty()) { - removeMessages(groupId, ids); - } - updateGroup(groupId, lastModifiedUpdate()); - } - - @Override - public @Nullable Message getMessageFromGroup(Object groupId, UUID messageId) { - Query query = - Query.query( - Criteria.where(MessageDocumentFields.MESSAGE_ID).is(messageId) - .and(MessageDocumentFields.GROUP_ID).is(groupId)); - MessageDocument document = getMongoTemplate().findOne(query, MessageDocument.class, this.collectionName); - return document != null ? document.getMessage() : null; - } - - @Override - protected boolean doRemoveMessageFromGroupById(Object groupId, UUID messageId) { - Query query = - Query.query( - Criteria.where(MessageDocumentFields.MESSAGE_ID).is(messageId) - .and(MessageDocumentFields.GROUP_ID).is(groupId)); - return getMongoTemplate() - .remove(query, this.collectionName) - .wasAcknowledged(); - } - - private void removeMessages(Object groupId, Collection ids) { - Query query = groupIdQuery(groupId) - .addCriteria(Criteria.where(MessageDocumentFields.MESSAGE_ID).in(ids.toArray())); - getMongoTemplate().remove(query, this.collectionName); - } - - @Override - protected @Nullable Message doPollMessageFromGroup(final Object groupId) { - Sort sort = Sort.by(MessageDocumentFields.LAST_MODIFIED_TIME, MessageDocumentFields.SEQUENCE); - Query query = groupIdQuery(groupId).with(sort); - MessageDocument document = getMongoTemplate().findAndRemove(query, MessageDocument.class, collectionName); - Message message = null; - if (document != null) { - message = document.getMessage(); - updateGroup(groupId, lastModifiedUpdate()); - } - return message; - } - - @Override - protected void doSetLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) { - updateGroup(groupId, lastModifiedUpdate().set(MessageDocumentFields.LAST_RELEASED_SEQUENCE, sequenceNumber)); - } - - @Override - protected void doSetGroupCondition(Object groupId, String condition) { - updateGroup(groupId, lastModifiedUpdate().set("condition", condition)); - } - - @Override - protected void doCompleteGroup(Object groupId) { - updateGroup(groupId, lastModifiedUpdate().set(MessageDocumentFields.COMPLETE, true)); - } - - @Override - public Iterator iterator() { - Query query = Query.query(Criteria.where(MessageDocumentFields.GROUP_ID).exists(true)); - Iterable groupIds = - getMongoTemplate() - .findDistinct(query, MessageDocumentFields.GROUP_ID, this.collectionName, Object.class); - - return StreamSupport.stream(groupIds.spliterator(), false) - .map(this::getMessageGroup) - .iterator(); - - } - - @Override - @ManagedAttribute - public int getMessageCountForAllMessageGroups() { - Query query = Query.query(Criteria.where(MessageDocumentFields.MESSAGE_ID).exists(true) - .and(MessageDocumentFields.GROUP_ID).exists(true)); - long count = getMongoTemplate().count(query, this.collectionName); - Assert.isTrue(count <= Integer.MAX_VALUE, "Message count is out of Integer's range"); - return (int) count; - } - - @Override - @ManagedAttribute - public int getMessageGroupCount() { - Query query = Query.query(Criteria.where(MessageDocumentFields.GROUP_ID).exists(true)); - return getMongoTemplate() - .findDistinct(query, MessageDocumentFields.GROUP_ID, this.collectionName, Object.class) - .size(); - } - - @Override - public @Nullable Message getOneMessageFromGroup(Object groupId) { - Query query = groupOrderQuery(groupId); - MessageDocument messageDocument = getMongoTemplate().findOne(query, MessageDocument.class, this.collectionName); - if (messageDocument != null) { - return messageDocument.getMessage(); - } - else { - return null; - } - } - - @Override - public Collection> getMessagesForGroup(Object groupId) { - Query query = groupOrderQuery(groupId); - List documents = getMongoTemplate().find(query, MessageDocument.class, this.collectionName); - - return documents.stream() - .map(MessageDocument::getMessage) - .collect(Collectors.toList()); - } - - @Override - public Stream> streamMessagesForGroup(Object groupId) { - Query query = groupOrderQuery(groupId); - Stream documents = - getMongoTemplate() - .stream(query, MessageDocument.class, this.collectionName); - - return documents.map(MessageDocument::getMessage); - } - - private void updateGroup(Object groupId, Update update) { - getMongoTemplate() - .findAndModify(groupOrderQuery(groupId), update, FindAndModifyOptions.none(), Map.class, - this.collectionName); - } - - private static Update lastModifiedUpdate() { - return Update.update(MessageDocumentFields.LAST_MODIFIED_TIME, System.currentTimeMillis()); - } - - private static Query groupOrderQuery(Object groupId) { - Sort sort = Sort.by(Sort.Direction.DESC, MessageDocumentFields.LAST_MODIFIED_TIME, - MessageDocumentFields.SEQUENCE); - return groupIdQuery(groupId).with(sort); - } - -} - diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MessageDocument.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MessageDocument.java deleted file mode 100644 index 34b7353bf37..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MessageDocument.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.Objects; -import java.util.UUID; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.annotation.AccessType; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.PersistenceCreator; -import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * The entity class to wrap {@link org.springframework.messaging.Message} to the MongoDB document. - * - * @author Artem Bilan - * @since 4.0 - */ -@Document -@AccessType(AccessType.Type.PROPERTY) -public class MessageDocument { - - /* - * Needed as a persistence property to suppress 'Cannot determine IsNewStrategy' MappingException - * when the application context is configured with auditing. The document is not - * currently Auditable. - */ - @Id - private @Nullable String _id; - - private final Message message; - - private final UUID messageId; - - private @Nullable Integer priority; - - private Long createdTime = 0L; - - private Long groupCreatedTime = 0L; - - private @Nullable Object groupId; - - private Long lastModifiedTime = 0L; - - private Boolean complete = false; - - private Integer lastReleasedSequence = 0; - - private @Nullable String condition; - - private long sequence; - - public MessageDocument(Message message) { - this(message, Objects.requireNonNull(message.getHeaders().getId())); - } - - /** - * The special persistence constructor to populate a {@code final} - * properties from the source MongoDB document. - * @param message the {@link Message} this document is assigned. - * @param messageId the id of the {@link Message} as a separate persistent property. - * @since 5.1 - */ - @PersistenceCreator - MessageDocument(Message message, UUID messageId) { - Assert.notNull(message, "'message' must not be null"); - Assert.notNull(messageId, "'message' ID header must not be null"); - this.message = message; - this.messageId = messageId; - } - - public Message getMessage() { - return this.message; - } - - public UUID getMessageId() { - return this.messageId; - } - - public void setGroupId(Object groupId) { - this.groupId = groupId; - } - - public void setPriority(@Nullable Integer priority) { - this.priority = priority; - } - - public Long getLastModifiedTime() { - return this.lastModifiedTime; - } - - public void setLastModifiedTime(long lastModifiedTime) { - this.lastModifiedTime = lastModifiedTime; - } - - public Long getCreatedTime() { - return this.createdTime; - } - - public void setCreatedTime(long createdTime) { - this.createdTime = createdTime; - } - - public Long getGroupCreatedTime() { - return this.groupCreatedTime; - } - - public void setGroupCreatedTime(long groupCreatedTime) { - this.groupCreatedTime = groupCreatedTime; - } - - public Boolean isComplete() { - return this.complete; - } - - public void setComplete(boolean complete) { - this.complete = complete; - } - - public Integer getLastReleasedSequence() { - return this.lastReleasedSequence; - } - - public void setLastReleasedSequence(int lastReleasedSequence) { - this.lastReleasedSequence = lastReleasedSequence; - } - - public void setSequence(long sequence) { - this.sequence = sequence; - } - - public @Nullable Integer getPriority() { - return this.priority; - } - - public @Nullable Object getGroupId() { - return this.groupId; - } - - @Nullable - public String getCondition() { - return this.condition; - } - - public void setCondition(String condition) { - this.condition = condition; - } - - public long getSequence() { - return this.sequence; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MessageDocumentFields.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MessageDocumentFields.java deleted file mode 100644 index a119cf1d432..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MessageDocumentFields.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -/** - * @author Artem Bilan - * - * @since 4.0 - */ -public final class MessageDocumentFields { - - public static final String MESSAGE_ID = "messageId"; - - public static final String PRIORITY = "priority"; - - public static final String GROUP_ID = "groupId"; - - public static final String LAST_MODIFIED_TIME = "lastModifiedTime"; - - public static final String SEQUENCE = "sequence"; - - public static final String LAST_RELEASED_SEQUENCE = "lastReleasedSequence"; - - public static final String COMPLETE = "complete"; - - private MessageDocumentFields() { - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbChannelMessageStore.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbChannelMessageStore.java deleted file mode 100644 index 4f367f0bb93..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbChannelMessageStore.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.index.Index; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.PriorityCapableChannelMessageStore; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * MongoDB {@link PriorityCapableChannelMessageStore} implementation. - * This message store shall be used for message channels only. - * - *

Provide the {@link #priorityEnabled} option to allow to poll messages via {@code priority} manner. - * - *

As a priority document field the {@link org.springframework.integration.IntegrationMessageHeaderAccessor#PRIORITY} - * message header is used. - * - *

The same collection can be used for {@code org.springframework.integration.channel.QueueChannel}s and - * {@code org.springframework.integration.channel.PriorityChannel}s, but the different instances of - * {@link MongoDbChannelMessageStore} should be used for those cases, and the last one with - * {@code priorityEnabled = true} option. - * - * @author Artem Bilan - * @author Adama Sorho - * @author Youbin Wu - * - * @since 4.0 - */ -public class MongoDbChannelMessageStore extends AbstractConfigurableMongoDbMessageStore - implements PriorityCapableChannelMessageStore { - - /** - * The default conventional collection name. - */ - public static final String DEFAULT_COLLECTION_NAME = "channelMessages"; - - private boolean priorityEnabled; - - public MongoDbChannelMessageStore(MongoTemplate mongoTemplate) { - this(mongoTemplate, DEFAULT_COLLECTION_NAME); - } - - public MongoDbChannelMessageStore(MongoTemplate mongoTemplate, String collectionName) { - super(mongoTemplate, collectionName); - } - - public MongoDbChannelMessageStore(MongoDatabaseFactory mongoDbFactory) { - this(mongoDbFactory, null, DEFAULT_COLLECTION_NAME); - } - - public MongoDbChannelMessageStore(MongoDatabaseFactory mongoDbFactory, - MappingMongoConverter mappingMongoConverter) { - - this(mongoDbFactory, mappingMongoConverter, DEFAULT_COLLECTION_NAME); - } - - public MongoDbChannelMessageStore(MongoDatabaseFactory mongoDbFactory, String collectionName) { - this(mongoDbFactory, null, collectionName); - } - - public MongoDbChannelMessageStore(MongoDatabaseFactory mongoDbFactory, - @Nullable MappingMongoConverter mappingMongoConverter, String collectionName) { - - super(mongoDbFactory, mappingMongoConverter, collectionName); - } - - public void setPriorityEnabled(boolean priorityEnabled) { - this.priorityEnabled = priorityEnabled; - } - - @Override - public boolean isPriorityEnabled() { - return this.priorityEnabled; - } - - @Override - protected void createIndexes() { - super.createIndexes(); - getMongoTemplate() - .indexOps(this.collectionName) - .createIndex(new Index(MessageDocumentFields.GROUP_ID, Sort.Direction.ASC) - .on(MessageDocumentFields.PRIORITY, Sort.Direction.DESC) - .on(MessageDocumentFields.LAST_MODIFIED_TIME, Sort.Direction.ASC) - .on(MessageDocumentFields.SEQUENCE, Sort.Direction.ASC)); - } - - @Override - public MessageGroup addMessageToGroup(Object groupId, Message message) { - Assert.notNull(groupId, "'groupId' must not be null"); - Assert.notNull(message, "'message' must not be null"); - - MessageDocument document = new MessageDocument(message); - document.setGroupId(groupId); - document.setCreatedTime(System.currentTimeMillis()); - document.setLastModifiedTime(System.currentTimeMillis()); - if (this.priorityEnabled) { - document.setPriority(message.getHeaders().get(IntegrationMessageHeaderAccessor.PRIORITY, Integer.class)); - } - document.setSequence(getNextId()); - - addMessageDocument(document); - return getMessageGroup(groupId); - } - - /** - * Not fully used. Only wraps the provided group id. - */ - @Override - public MessageGroup getMessageGroup(Object groupId) { - return getMessageGroupFactory().create(groupId); - } - - @Override - protected @Nullable Message doPollMessageFromGroup(Object groupId) { - Assert.notNull(groupId, "'groupId' must not be null"); - - Sort sort = Sort.by(MessageDocumentFields.LAST_MODIFIED_TIME, MessageDocumentFields.SEQUENCE); - if (this.priorityEnabled) { - sort = Sort.by(Sort.Direction.DESC, MessageDocumentFields.PRIORITY).and(sort); - } - Query query = groupIdQuery(groupId).with(sort); - MessageDocument document = getMongoTemplate().findAndRemove(query, MessageDocument.class, this.collectionName); - Message message = null; - if (document != null) { - message = document.getMessage(); - } - return message; - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java deleted file mode 100644 index 0a72a2b9903..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java +++ /dev/null @@ -1,1001 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.mongodb.BasicDBList; -import com.mongodb.DBObject; -import org.bson.Document; -import org.bson.conversions.Bson; -import org.bson.types.Binary; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.serializer.support.SerializingConverter; -import org.springframework.data.annotation.Id; -import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.convert.WritingConverter; -import org.springframework.data.domain.Sort; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.BulkOperations; -import org.springframework.data.mongodb.core.FindAndModifyOptions; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.MongoCustomConversions; -import org.springframework.data.mongodb.core.index.Index; -import org.springframework.data.mongodb.core.index.IndexOperations; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.data.util.TypeInformation; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.message.AdviceMessage; -import org.springframework.integration.store.AbstractMessageGroupStore; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageMetadata; -import org.springframework.integration.store.MessageStore; -import org.springframework.integration.store.SimpleMessageGroup; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.MutableMessageBuilder; -import org.springframework.integration.support.converter.AllowListDeserializingConverter; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * An implementation of both the {@link MessageStore} and - * {@link org.springframework.integration.store.MessageGroupStore} - * strategies that relies upon MongoDB for persistence. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Sean Brandt - * @author Jodie StJohn - * @author Gary Russell - * @author Artem Bilan - * @author Youbin Wu - * - * @since 2.1 - */ -public class MongoDbMessageStore extends AbstractMessageGroupStore - implements MessageStore, BeanClassLoaderAware, ApplicationContextAware, InitializingBean { - - public static final String SEQUENCE_NAME = "messagesSequence"; - - private static final String HEADERS = "headers"; - - private static final String UNCHECKED = "unchecked"; - - private static final String DEFAULT_COLLECTION_NAME = "messages"; - - private static final String GROUP_ID_KEY = "_groupId"; - - private static final String GROUP_COMPLETE_KEY = "_group_complete"; - - private static final String LAST_RELEASED_SEQUENCE_NUMBER = "_last_released_sequence"; - - private static final String GROUP_TIMESTAMP_KEY = "_group_timestamp"; - - private static final String GROUP_UPDATE_TIMESTAMP_KEY = "_group_update_timestamp"; - - private static final String CREATED_DATE = "_createdDate"; - - private static final String SEQUENCE = "sequence"; - - private final MongoTemplate template; - - private final MessageReadingMongoConverter converter; - - private final String collectionName; - - @SuppressWarnings("NullAway.Init") - private ClassLoader classLoader; - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - private String @Nullable [] allowedPatterns; - - /** - * Create a MongoDbMessageStore using the provided {@link MongoDatabaseFactory}.and the default collection name. - * @param mongoDbFactory The mongodb factory. - */ - public MongoDbMessageStore(MongoDatabaseFactory mongoDbFactory) { - this(mongoDbFactory, null); - } - - /** - * Create a MongoDbMessageStore using the provided {@link MongoDatabaseFactory} and collection name. - * @param mongoDbFactory The mongodb factory. - * @param collectionName The collection name. - */ - public MongoDbMessageStore(MongoDatabaseFactory mongoDbFactory, @Nullable String collectionName) { - Assert.notNull(mongoDbFactory, "mongoDbFactory must not be null"); - this.converter = new MessageReadingMongoConverter(mongoDbFactory, new MongoMappingContext()); - this.template = new MongoTemplate(mongoDbFactory, this.converter); - this.collectionName = (StringUtils.hasText(collectionName)) ? collectionName : DEFAULT_COLLECTION_NAME; - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - Assert.notNull(classLoader, "classLoader must not be null"); - this.classLoader = classLoader; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - /** - * Add patterns for packages/classes that are allowed to be deserialized. A class can - * be fully qualified or a wildcard '*' is allowed at the beginning or end of the - * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. - * @param patterns the patterns. - * @since 5.4 - */ - public void addAllowedPatterns(String @Nullable ... patterns) { - this.allowedPatterns = patterns != null ? Arrays.copyOf(patterns, patterns.length) : null; - } - - /** - * Configure a set of converters to use in the {@link MappingMongoConverter}. - * Must be instances of {@code org.springframework.core.convert.converter.Converter}, - * {@code org.springframework.core.convert.converter.ConverterFactory}, - * {@code org.springframework.core.convert.converter.GenericConverter} or - * {@code org.springframework.data.convert.ConverterBuilder.ConverterAware}. - * @param customConverters the converters to use. - * @since 5.1.6 - */ - public void setCustomConverters(Object... customConverters) { - this.converter.setCustomConverters(customConverters); - } - - @Override - public void afterPropertiesSet() { - this.converter.setApplicationContext(this.applicationContext); - - this.converter.afterPropertiesSet(); - - IndexOperations indexOperations = this.template.indexOps(this.collectionName); - - indexOperations.createIndex( - new Index(GROUP_ID_KEY, Sort.Direction.ASC) - .on(GROUP_UPDATE_TIMESTAMP_KEY, Sort.Direction.DESC) - .on(SEQUENCE, Sort.Direction.DESC)); - } - - @Override - public Message addMessage(Message message) { - addMessageDocument(new MessageWrapper(message)); - return message; - } - - private void addMessageDocument(MessageWrapper document) { - UUID messageId = (UUID) document.headers.get(MessageHeaders.ID); - Assert.notNull(messageId, "ID header must not be null"); - Query query = whereMessageIdIsAndGroupIdIs(messageId, document.get_GroupId()); - if (!this.template.exists(query, MessageWrapper.class, this.collectionName)) { - if (document.get_Group_timestamp() == 0) { - document.set_Group_timestamp(System.currentTimeMillis()); - } - document.set_message_timestamp(System.currentTimeMillis()); - this.template.insert(document, this.collectionName); - } - } - - @Override - public @Nullable Message getMessage(UUID id) { - MessageWrapper messageWrapper = - this.template.findOne(whereMessageIdIs(id), MessageWrapper.class, this.collectionName); - return (messageWrapper != null) ? messageWrapper.getMessage() : null; - } - - @Override - public @Nullable MessageMetadata getMessageMetadata(UUID id) { - MessageWrapper messageWrapper = - this.template.findOne(whereMessageIdIs(id), MessageWrapper.class, this.collectionName); - if (messageWrapper != null) { - MessageMetadata messageMetadata = new MessageMetadata(id); - messageMetadata.setTimestamp(messageWrapper.get_message_timestamp()); - return messageMetadata; - } - else { - return null; - } - } - - @Override - @ManagedAttribute - public long getMessageCount() { - Query query = Query.query(Criteria.where("headers.id").exists(true).and(GROUP_ID_KEY).exists(false)); - return this.template.getCollection(this.collectionName).countDocuments(query.getQueryObject()); - } - - @Override - public @Nullable Message removeMessage(UUID id) { - Query query = Query.query(Criteria.where("headers.id").is(id).and(GROUP_ID_KEY).exists(false)); - MessageWrapper messageWrapper = this.template.findAndRemove(query, MessageWrapper.class, this.collectionName); - return (messageWrapper != null ? messageWrapper.getMessage() : null); - } - - @Override - public MessageGroup getMessageGroup(Object groupId) { - Query query = whereGroupIdOrder(groupId); - MessageWrapper messageWrapper = this.template.findOne(query, MessageWrapper.class, this.collectionName); - - if (messageWrapper != null) { - long createdTime = messageWrapper.get_Group_timestamp(); - long lastModifiedTime = messageWrapper.get_Group_update_timestamp(); - boolean complete = messageWrapper.get_Group_complete(); - int lastReleasedSequence = messageWrapper.get_LastReleasedSequenceNumber(); - - MessageGroup messageGroup = getMessageGroupFactory() - .create(this, groupId, createdTime, complete); - messageGroup.setLastModified(lastModifiedTime); - messageGroup.setLastReleasedMessageSequenceNumber(lastReleasedSequence); - messageGroup.setCondition(messageWrapper.getCondition()); - return messageGroup; - - } - else { - return new SimpleMessageGroup(groupId); - } - } - - @Override - protected void doAddMessagesToGroup(Object groupId, Message... messages) { - Query query = whereGroupIdOrder(groupId); - MessageWrapper messageDocument = this.template.findOne(query, MessageWrapper.class, this.collectionName); - - long createdTime = System.currentTimeMillis(); - int lastReleasedSequence = 0; - boolean complete = false; - String condition = null; - if (messageDocument != null) { - createdTime = messageDocument.get_Group_timestamp(); - lastReleasedSequence = messageDocument.get_LastReleasedSequenceNumber(); - complete = messageDocument.get_Group_complete(); - condition = messageDocument.getCondition(); - } - - for (Message message : messages) { - MessageWrapper wrapper = new MessageWrapper(message); - wrapper.set_GroupId(groupId); - wrapper.set_Group_timestamp(createdTime); - wrapper.set_Group_update_timestamp(messageDocument == null ? createdTime : System.currentTimeMillis()); - wrapper.set_Group_complete(complete); - wrapper.set_LastReleasedSequenceNumber(lastReleasedSequence); - wrapper.setSequence(getNextId()); - if (condition != null) { - wrapper.setCondition(condition); - } - - addMessageDocument(wrapper); - } - } - - @Override - protected void doRemoveMessagesFromGroup(Object groupId, Collection> messages) { - Collection ids = new ArrayList<>(); - for (Message messageToRemove : messages) { - UUID id = messageToRemove.getHeaders().getId(); - Assert.state(id != null, () -> "The message 'id' must notbe null:" + messageToRemove); - ids.add(id); - if (ids.size() >= getRemoveBatchSize()) { - bulkRemove(groupId, ids); - ids.clear(); - } - } - if (!ids.isEmpty()) { - bulkRemove(groupId, ids); - } - updateGroup(groupId, lastModifiedUpdate()); - } - - private void bulkRemove(Object groupId, Collection ids) { - BulkOperations bulkOperations = this.template.bulkOps(BulkOperations.BulkMode.ORDERED, this.collectionName); - - for (UUID id : ids) { - bulkOperations.remove(whereMessageIdIsAndGroupIdIs(id, groupId)); - } - bulkOperations.execute(); - } - - @Override - public @Nullable Message getMessageFromGroup(Object groupId, UUID messageId) { - MessageWrapper messageWrapper = - this.template.findOne(whereMessageIdIsAndGroupIdIs(messageId, groupId), - MessageWrapper.class, this.collectionName); - return (messageWrapper != null) ? messageWrapper.getMessage() : null; - } - - @Override - protected boolean doRemoveMessageFromGroupById(Object groupId, UUID messageId) { - return this.template.remove(whereMessageIdIsAndGroupIdIs(messageId, groupId), this.collectionName) - .wasAcknowledged(); - } - - @Override - protected void doRemoveMessageGroup(Object groupId) { - this.template.remove(whereGroupIdIs(groupId), this.collectionName); - } - - @Override - public Iterator iterator() { - List messageGroups = new ArrayList<>(); - - Query query = Query.query(Criteria.where(GROUP_ID_KEY).exists(true)); - - Iterable groupIds = this.template.findDistinct(query, GROUP_ID_KEY, this.collectionName, Object.class); - - for (Object groupId : groupIds) { - messageGroups.add(getMessageGroup(groupId)); - } - - return messageGroups.iterator(); - } - - @Override - protected @Nullable Message doPollMessageFromGroup(final Object groupId) { - Query query = whereGroupIdIs(groupId).with(Sort.by(GROUP_UPDATE_TIMESTAMP_KEY, SEQUENCE)); - MessageWrapper messageWrapper = this.template.findAndRemove(query, MessageWrapper.class, this.collectionName); - Message message = null; - if (messageWrapper != null) { - message = messageWrapper.getMessage(); - } - updateGroup(groupId, lastModifiedUpdate()); - return message; - } - - @Override - public int messageGroupSize(Object groupId) { - long lCount = this.template.count(new Query(Criteria.where(GROUP_ID_KEY).is(groupId)), this.collectionName); - Assert.isTrue(lCount <= Integer.MAX_VALUE, "Message count is out of Integer's range"); - return (int) lCount; - } - - @Override - protected void doSetGroupCondition(Object groupId, String condition) { - updateGroup(groupId, lastModifiedUpdate().set("_condition", condition)); - } - - @Override - protected void doSetLastReleasedSequenceNumberForGroup(Object groupId, int sequenceNumber) { - updateGroup(groupId, lastModifiedUpdate().set(LAST_RELEASED_SEQUENCE_NUMBER, sequenceNumber)); - } - - @Override - protected void doCompleteGroup(Object groupId) { - this.updateGroup(groupId, lastModifiedUpdate().set(GROUP_COMPLETE_KEY, true)); - } - - @Override - public @Nullable Message getOneMessageFromGroup(Object groupId) { - Query query = whereGroupIdOrder(groupId); - MessageWrapper messageWrapper = this.template.findOne(query, MessageWrapper.class, this.collectionName); - if (messageWrapper != null) { - return messageWrapper.getMessage(); - } - else { - return null; - } - } - - @Override - public Collection> getMessagesForGroup(Object groupId) { - Query query = whereGroupIdOrder(groupId); - List messageWrappers = this.template.find(query, MessageWrapper.class, this.collectionName); - - return messageWrappers.stream() - .map(MessageWrapper::getMessage) - .collect(Collectors.toList()); - } - - @Override - public Stream> streamMessagesForGroup(Object groupId) { - Query query = whereGroupIdOrder(groupId); - Stream messageWrappers = - this.template.stream(query, MessageWrapper.class, this.collectionName); - - return messageWrappers.map(MessageWrapper::getMessage); - } - - @Override - @ManagedAttribute - public int getMessageCountForAllMessageGroups() { - Query query = Query.query(Criteria.where(GROUP_ID_KEY).exists(true)); - return (int) this.template.count(query, this.collectionName); - } - - @Override - @ManagedAttribute - public int getMessageGroupCount() { - Query query = Query.query(Criteria.where(GROUP_ID_KEY).exists(true)); - return this.template.findDistinct(query, GROUP_ID_KEY, this.collectionName, Object.class) - .size(); - } - - private static Update lastModifiedUpdate() { - return Update.update(GROUP_UPDATE_TIMESTAMP_KEY, System.currentTimeMillis()); - } - - /* - * Common Queries - */ - - private static Query whereMessageIdIs(UUID id) { - return new Query(Criteria.where("headers.id").is(id)); - } - - private static Query whereMessageIdIsAndGroupIdIs(UUID id, @Nullable Object groupId) { - return new Query(Criteria.where("headers.id").is(id).and(GROUP_ID_KEY).is(groupId)); - } - - private static Query whereGroupIdOrder(Object groupId) { - return whereGroupIdIs(groupId).with(Sort.by(Sort.Direction.DESC, GROUP_UPDATE_TIMESTAMP_KEY, SEQUENCE)); - } - - private static Query whereGroupIdIs(Object groupId) { - return new Query(Criteria.where(GROUP_ID_KEY).is(groupId)); - } - - private void updateGroup(Object groupId, Update update) { - Query query = whereGroupIdIs(groupId).with(Sort.by(Sort.Direction.DESC, GROUP_UPDATE_TIMESTAMP_KEY, SEQUENCE)); - this.template.findAndModify(query, update, FindAndModifyOptions.none(), Map.class, this.collectionName); - } - - @SuppressWarnings("NullAway") - private long getNextId() { - Query query = Query.query(Criteria.where("_id").is(SEQUENCE_NAME)); - query.fields().include(SEQUENCE); - return ((Number) this.template.findAndModify(query, - new Update().inc(SEQUENCE, 1L), - FindAndModifyOptions.options().returnNew(true).upsert(true), - Map.class, this.collectionName) - .get(SEQUENCE)) - .longValue(); - } - - @SuppressWarnings({UNCHECKED, "NullAway"}) - private static void enhanceHeaders(MessageHeaders messageHeaders, Map headers) { - Map innerMap = - (Map) new DirectFieldAccessor(messageHeaders).getPropertyValue(HEADERS); - // using reflection to set ID and TIMESTAMP since they are immutable through MessageHeaders - Object idHeader = headers.get(MessageHeaders.ID); - if (idHeader != null) { - innerMap.put(MessageHeaders.ID, idHeader); - } - Object tsHeader = headers.get(MessageHeaders.TIMESTAMP); - if (tsHeader != null) { - innerMap.put(MessageHeaders.TIMESTAMP, tsHeader); - } - } - - @SuppressWarnings(UNCHECKED) - private static Map asMap(Bson bson) { - if (bson instanceof Document) { - return (Document) bson; - } - - if (bson instanceof DBObject) { - return ((DBObject) bson).toMap(); - } - - throw new IllegalArgumentException( - String.format("Cannot read %s. as map. Given Bson must be a Document or DBObject!", bson.getClass())); - } - - /** - * Custom implementation of the {@link MappingMongoConverter} strategy. - */ - private final class MessageReadingMongoConverter extends MappingMongoConverter { - - private static final String CLASS = "_class"; - - private Object @Nullable [] customConverters; - - MessageReadingMongoConverter(MongoDatabaseFactory mongoDbFactory, - MappingContext, MongoPersistentProperty> mappingContext) { - super(new DefaultDbRefResolver(mongoDbFactory), mappingContext); - } - - void setCustomConverters(Object @Nullable ... customConverters) { - this.customConverters = - customConverters != null ? Arrays.copyOf(customConverters, customConverters.length) : null; - } - - @Override - public void afterPropertiesSet() { - List converters = new ArrayList<>(); - converters.add(new MessageHistoryToDocumentConverter()); - converters.add(new DocumentToMessageHistoryConverter()); - converters.add(new DocumentToGenericMessageConverter()); - converters.add(new DocumentToMutableMessageConverter()); - DocumentToErrorMessageConverter docToErrorMessageConverter = new DocumentToErrorMessageConverter(); - if (MongoDbMessageStore.this.allowedPatterns != null) { - docToErrorMessageConverter.deserializingConverter - .addAllowedPatterns(MongoDbMessageStore.this.allowedPatterns); - } - converters.add(docToErrorMessageConverter); - converters.add(new DocumentToAdviceMessageConverter()); - converters.add(new ThrowableToBytesConverter()); - - if (this.customConverters != null) { - Collections.addAll(converters, this.customConverters); - } - - setCustomConversions(new MongoCustomConversions(converters)); - super.afterPropertiesSet(); - } - - @Override - public void write(Object source, Bson target) { - Assert.isInstanceOf(MessageWrapper.class, source); - - asMap(target).put(CREATED_DATE, System.currentTimeMillis()); - - super.write(source, target); - } - - @Override - @SuppressWarnings({UNCHECKED}) - public S read(Class clazz, Bson source) { - if (!MessageWrapper.class.equals(clazz)) { - return super.read(clazz, source); - } - - return (S) readAsMessageWrapper(source); - } - - private MessageWrapper readAsMessageWrapper(Bson source) { - Map sourceMap = asMap(source); - Message message; - Object messageType = sourceMap.get("_messageType"); - if (messageType == null) { - messageType = GenericMessage.class.getName(); - } - try { - message = (Message) read(ClassUtils.forName(messageType.toString(), - MongoDbMessageStore.this.classLoader), source); - } - catch (ClassNotFoundException e) { - throw new IllegalStateException("failed to load class: " + messageType, e); - } - - Long groupTimestamp = (Long) sourceMap.get(GROUP_TIMESTAMP_KEY); - Long lastModified = (Long) sourceMap.get(GROUP_UPDATE_TIMESTAMP_KEY); - Integer lastReleasedSequenceNumber = (Integer) sourceMap.get(LAST_RELEASED_SEQUENCE_NUMBER); - Boolean completeGroup = (Boolean) sourceMap.get(GROUP_COMPLETE_KEY); - - MessageWrapper wrapper = new MessageWrapper(message); - - if (sourceMap.containsKey(GROUP_ID_KEY)) { - wrapper.set_GroupId(sourceMap.get(GROUP_ID_KEY)); - } - if (groupTimestamp != null) { - wrapper.set_Group_timestamp(groupTimestamp); - } - if (lastModified != null) { - wrapper.set_Group_update_timestamp(lastModified); - } - if (lastReleasedSequenceNumber != null) { - wrapper.set_LastReleasedSequenceNumber(lastReleasedSequenceNumber); - } - - if (completeGroup != null) { - wrapper.set_Group_complete(completeGroup); - } - wrapper.setCondition((String) sourceMap.get("_condition")); - - return wrapper; - - } - - @Override - @SuppressWarnings({UNCHECKED}) - protected S read(TypeInformation type, Bson source) { - if (!MessageWrapper.class.equals(type.getType())) { - return super.read(type, source); - } - - return (S) readAsMessageWrapper(source); - } - - private Map normalizeHeaders(Map headers) { - Map normalizedHeaders = new HashMap<>(); - for (Entry entry : headers.entrySet()) { - String headerName = entry.getKey(); - Object headerValue = entry.getValue(); - if (headerValue instanceof Bson source) { - Map document = asMap(source); - try { - Class typeClass; - if (document.containsKey(CLASS)) { - Object type = document.get(CLASS); - typeClass = ClassUtils.forName(type.toString(), MongoDbMessageStore.this.classLoader); - } - else if (source instanceof BasicDBList) { - typeClass = List.class; - } - else { - throw new IllegalStateException("Unsupported 'Bson' type: " + source.getClass()); - } - normalizedHeaders.put(headerName, super.read(typeClass, source)); - } - catch (Exception e) { - logger.warn("Header '" + headerName + "' could not be deserialized.", e); - } - } - else { - normalizedHeaders.put(headerName, headerValue); - } - } - return normalizedHeaders; - } - - private Object extractPayload(Bson source) { - Object payload = asMap(source).get("payload"); - Objects.requireNonNull(payload); - if (payload instanceof Bson payloadObject) { - Object payloadType = asMap(payloadObject).get(CLASS); - Objects.requireNonNull(payloadType); - try { - Class payloadClass = - ClassUtils.forName(payloadType.toString(), MongoDbMessageStore.this.classLoader); - payload = read(payloadClass, payloadObject); - } - catch (Exception e) { - throw new IllegalStateException("failed to load class: " + payloadType, e); - } - } - return payload; - } - - } - - @WritingConverter - private static final class MessageHistoryToDocumentConverter implements Converter { - - MessageHistoryToDocumentConverter() { - } - - @Override - public Document convert(MessageHistory source) { - return new Document("components", source) - .append("_class", MessageHistory.class.getName()); - } - - } - - @ReadingConverter - private static final class DocumentToMessageHistoryConverter implements Converter { - - private static final Constructor MESSAGE_HISTORY_CONSTRUCTOR; - - static { - try { - MESSAGE_HISTORY_CONSTRUCTOR = MessageHistory.class.getDeclaredConstructor(List.class); - } - catch (NoSuchMethodException ex) { - throw new IllegalStateException(ex); - } - } - - DocumentToMessageHistoryConverter() { - } - - @Override - @SuppressWarnings("unchecked") - public MessageHistory convert(Document source) { - List components = (List) source.get("components"); - Objects.requireNonNull(components); - List historyEntries = new ArrayList<>(components.size()); - for (Document component : components) { - MessageHistory.Entry entry = new MessageHistory.Entry(); - for (Entry componentEntry : component.entrySet()) { - entry.setProperty(componentEntry.getKey(), componentEntry.getValue().toString()); - } - historyEntries.add(entry); - } - return BeanUtils.instantiateClass(MESSAGE_HISTORY_CONSTRUCTOR, historyEntries); - } - - } - - @ReadingConverter - private final class DocumentToGenericMessageConverter implements Converter> { - - DocumentToGenericMessageConverter() { - } - - @Override - public GenericMessage convert(Document source) { - @SuppressWarnings(UNCHECKED) - Map messageHeaders = Objects.requireNonNull((Map) source.get(HEADERS)); - Map headers = - MongoDbMessageStore.this.converter.normalizeHeaders(messageHeaders); - - GenericMessage message = - new GenericMessage<>(MongoDbMessageStore.this.converter.extractPayload(source), headers); - enhanceHeaders(message.getHeaders(), headers); - return message; - } - - } - - @ReadingConverter - private final class DocumentToMutableMessageConverter implements Converter> { - - DocumentToMutableMessageConverter() { - } - - @Override - public MutableMessage convert(Document source) { - @SuppressWarnings(UNCHECKED) - Map messageHeaders = Objects.requireNonNull((Map) source.get(HEADERS)); - Map headers = - MongoDbMessageStore.this.converter.normalizeHeaders(messageHeaders); - - Object payload = MongoDbMessageStore.this.converter.extractPayload(source); - return (MutableMessage) MutableMessageBuilder.withPayload(payload) - .copyHeaders(headers) - .build(); - } - - } - - @ReadingConverter - private final class DocumentToAdviceMessageConverter implements Converter> { - - DocumentToAdviceMessageConverter() { - } - - @Override - public AdviceMessage convert(Document source) { - @SuppressWarnings(UNCHECKED) - Map messageHeaders = Objects.requireNonNull((Map) source.get(HEADERS)); - Map headers = - MongoDbMessageStore.this.converter.normalizeHeaders(messageHeaders); - - Message inputMessage = null; - - if (source.get("inputMessage") != null) { - Bson inputMessageObject = (Bson) source.get("inputMessage"); - Object inputMessageType = asMap(inputMessageObject).get("_class"); - Objects.requireNonNull(inputMessageType); - try { - Class messageClass = ClassUtils.forName(inputMessageType.toString(), - MongoDbMessageStore.this.classLoader); - inputMessage = (Message) MongoDbMessageStore.this.converter.read(messageClass, - inputMessageObject); - } - catch (Exception e) { - throw new IllegalStateException("failed to load class: " + inputMessageType, e); - } - } - - Assert.notNull(inputMessage, "'inputMessage' must not be null"); - AdviceMessage message = new AdviceMessage<>( - MongoDbMessageStore.this.converter.extractPayload(source), headers, inputMessage); - enhanceHeaders(message.getHeaders(), headers); - - return message; - } - - } - - @ReadingConverter - private final class DocumentToErrorMessageConverter implements Converter { - - private final AllowListDeserializingConverter deserializingConverter = new AllowListDeserializingConverter(); - - DocumentToErrorMessageConverter() { - } - - @Override - public ErrorMessage convert(Document source) { - @SuppressWarnings(UNCHECKED) - Map messageHeaders = Objects.requireNonNull((Map) source.get(HEADERS)); - Map headers = - MongoDbMessageStore.this.converter.normalizeHeaders(messageHeaders); - - Binary binary = (Binary) source.get("payload"); - Object payload = this.deserializingConverter.convert(Objects.requireNonNull(binary).getData()); - ErrorMessage message = new ErrorMessage((Throwable) payload, headers); // NOSONAR not null - enhanceHeaders(message.getHeaders(), headers); - - return message; - } - - } - - @WritingConverter - private static final class ThrowableToBytesConverter implements Converter { - - private final SerializingConverter serializingConverter = new SerializingConverter(); - - ThrowableToBytesConverter() { - } - - @Override - public byte[] convert(Throwable source) { - return this.serializingConverter.convert(source); - } - - } - - /** - * Wrapper class used for storing Messages in MongoDB along with their "group" metadata. - */ - private static final class MessageWrapper { - - private static final String UNUSED = "unused"; - - /* - * Needed as a persistence property to suppress 'Cannot determine IsNewStrategy' MappingException - * when the application context is configured with auditing. The document is not - * currently Auditable. - */ - @SuppressWarnings(UNUSED) - @Id - private @Nullable String _id; - - private volatile @Nullable Object _groupId; - - private final Message message; - - @SuppressWarnings(UNUSED) - private final String _messageType; - - @SuppressWarnings(UNUSED) - private final Object payload; - - @SuppressWarnings(UNUSED) - private final Map headers; - - @SuppressWarnings(UNUSED) - private final @Nullable Message inputMessage; - - private long _message_timestamp; - - private volatile long _group_timestamp; - - private volatile long _group_update_timestamp; - - private volatile int _last_released_sequence; - - private volatile boolean _group_complete; - - private volatile @Nullable String _condition; - - @SuppressWarnings(UNUSED) - private long sequence; - - MessageWrapper(Message message) { - Assert.notNull(message, "'message' must not be null"); - this.message = message; - this._messageType = message.getClass().getName(); - this.payload = message.getPayload(); - this.headers = message.getHeaders(); - if (message instanceof AdviceMessage) { - this.inputMessage = ((AdviceMessage) message).getInputMessage(); - } - else { - this.inputMessage = null; - } - } - - public int get_LastReleasedSequenceNumber() { // NOSONAR name - return this._last_released_sequence; - } - - public long get_Group_timestamp() { // NOSONAR name - return this._group_timestamp; - } - - public boolean get_Group_complete() { // NOSONAR name - return this._group_complete; - } - - @SuppressWarnings(UNUSED) - public @Nullable Object get_GroupId() { // NOSONAR name - return this._groupId; - } - - public Message getMessage() { - return this.message; - } - - public void set_GroupId(Object groupId) { // NOSONAR name - this._groupId = groupId; - } - - public void set_Group_timestamp(long groupTimestamp) { // NOSONAR name - this._group_timestamp = groupTimestamp; - } - - public long get_message_timestamp() { // NOSONAR name - return this._message_timestamp; - } - - public void set_message_timestamp(long _message_timestamp) { // NOSONAR name - this._message_timestamp = _message_timestamp; - } - - public long get_Group_update_timestamp() { // NOSONAR name - return this._group_update_timestamp; - } - - public void set_Group_update_timestamp(long lastModified) { // NOSONAR name - this._group_update_timestamp = lastModified; - } - - public void set_LastReleasedSequenceNumber(int lastReleasedSequenceNumber) { // NOSONAR name - this._last_released_sequence = lastReleasedSequenceNumber; - } - - public void set_Group_complete(boolean completedGroup) { // NOSONAR name - this._group_complete = completedGroup; - } - - public @Nullable String getCondition() { - return this._condition; - } - - public void setCondition(@Nullable String condition) { - this._condition = condition; - } - - public void setSequence(long sequence) { - this.sequence = sequence; - } - - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/package-info.java deleted file mode 100644 index 64c78cd5c4d..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to the MongoDB message store. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.store; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java deleted file mode 100644 index add94119f2e..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.support; - -import org.bson.types.Binary; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.ReadingConverter; -import org.springframework.integration.support.converter.AllowListDeserializingConverter; -import org.springframework.messaging.Message; - -/** - * @author Artem Bilan - * @author Gary Russell - * @since 5.0 - */ -@ReadingConverter -public class BinaryToMessageConverter implements Converter> { - - private final AllowListDeserializingConverter deserializingConverter = new AllowListDeserializingConverter(); - - @Override - public Message convert(Binary source) { - return (Message) this.deserializingConverter.convert(source.getData()); - } - - /** - * Add patterns for packages/classes that are allowed to be deserialized. A class can - * be fully qualified or a wildcard '*' is allowed at the beginning or end of the - * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. - * @param patterns the patterns. - * @since 5.4 - */ - public void addAllowedPatterns(String... patterns) { - this.deserializingConverter.addAllowedPatterns(patterns); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MessageToBinaryConverter.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MessageToBinaryConverter.java deleted file mode 100644 index f375895b27f..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MessageToBinaryConverter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.support; - -import java.util.Objects; - -import org.bson.types.Binary; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.serializer.support.SerializingConverter; -import org.springframework.data.convert.WritingConverter; -import org.springframework.messaging.Message; - -/** - * @author Artem Bilan - * @since 5.0 - */ -@WritingConverter -public class MessageToBinaryConverter implements Converter, Binary> { - - private final Converter serializingConverter = new SerializingConverter(); - - @Override - public Binary convert(Message source) { - return new Binary(Objects.requireNonNull(this.serializingConverter.convert(source))); - } - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoHeaders.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoHeaders.java deleted file mode 100644 index 2c9e6f88bad..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoHeaders.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.support; - -/** - * Pre-defined names and prefixes to be used - * for dealing with headers required by Mongo components. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.2 - */ -public final class MongoHeaders { - - private MongoHeaders() { - } - - /** - * A prefix for MongoDb-specific message headers. - */ - public static final String PREFIX = "mongo_"; - - /** - * The prefix for change stream event headers. - * @since 5.3 - */ - public static final String PREFIX_CHANGE_STREAM = PREFIX + "changeStream_"; - - /** - * The header for MongoDb collection name. - */ - public static final String COLLECTION_NAME = PREFIX + "collectionName"; - - /** - * The header for change stream event type. - * @since 5.3 - */ - public static final String CHANGE_STREAM_OPERATION_TYPE = PREFIX_CHANGE_STREAM + "operationType"; - - /** - * The header for change stream event timestamp. - * @since 5.3 - */ - public static final String CHANGE_STREAM_TIMESTAMP = PREFIX_CHANGE_STREAM + "timestamp"; - - /** - * The header for change stream event resume token. - * @since 5.3 - */ - public static final String CHANGE_STREAM_RESUME_TOKEN = PREFIX_CHANGE_STREAM + "resumeToken"; - -} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/package-info.java deleted file mode 100644 index f9bf8f2f40b..00000000000 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides supporting classes for this module. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mongodb.support; diff --git a/spring-integration-mongodb/src/main/resources/META-INF/spring.handlers b/spring-integration-mongodb/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index 2eeef287429..00000000000 --- a/spring-integration-mongodb/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/mongodb=org.springframework.integration.mongodb.config.MongoDbNamespaceHandler \ No newline at end of file diff --git a/spring-integration-mongodb/src/main/resources/META-INF/spring.schemas b/spring-integration-mongodb/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index 89fba33983b..00000000000 --- a/spring-integration-mongodb/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,20 +0,0 @@ -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-2.2.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-3.0.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.0.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.1.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.2.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.3.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-5.0.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-5.1.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-5.2.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -http\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-2.2.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-3.0.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.0.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.1.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.2.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-4.3.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-5.0.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-5.1.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb-5.2.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd -https\://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb.xsd=org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd diff --git a/spring-integration-mongodb/src/main/resources/META-INF/spring.tooling b/spring-integration-mongodb/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index 49bdf56465d..00000000000 --- a/spring-integration-mongodb/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration mongodb namespace -http\://www.springframework.org/schema/integration/mongodb@name=integration mongodb Namespace -http\://www.springframework.org/schema/integration/mongodb@prefix=int-mongodb -http\://www.springframework.org/schema/integration/mongodb@icon=org/springframework/integration/mongodb/config/spring-integration-mongodb.gif diff --git a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb.gif b/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb.gif deleted file mode 100644 index 351817f7b35..00000000000 Binary files a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb.gif and /dev/null differ diff --git a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd b/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd deleted file mode 100644 index 41c7214882f..00000000000 --- a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb.xsd +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - - - - - - - - Defines a Polling Channel Adapter for the - 'org.springframework.integration.mongodb.inbound.MongoDbMessageSource' - that queries data from the MongoDB collection and produces messages to the channel. - - - - - - - - - - - - String representation of MongoDb Query (e.g., - query="{'name' : 'Bob'}"). - Please refer to MongoDb documentation for more query samples - https://www.mongodb.org/display/DOCS/Querying - This attribute is - mutually exclusive with 'query-expression' attribute. - - - - - - - SpEL expression which should resolve to a String query (please refer to the 'query' - attribute), - or to an instance of MongoDb Query (e.q., query-expression="new BasicQuery('{''name'' : - ''Bob''}').limit(2)"). - This attribute is mutually exclusive with 'query' attribute. - - - - - - - String representation of MongoDb Update (e.g., update="{$set: {'name' : 'Bob'}"). - Please refer to MongoDb documentation for more query samples - https://www.mongodb.org/display/DOCS/Querying - This attribute is - mutually exclusive with 'update-expression' attribute. - - - - - - - SpEL expression which should resolve to a String update (please refer to the 'update' - attribute), - or to an instance of MongoDb Update (e.q., - update-expression="T(Update).update('name', 'Bob')"). - This attribute is mutually exclusive with 'update' attribute. - - - - - - - - The fully qualified name of the entity class to be passed to - find(..) or findOne(..) method MongoTemplate. - If this attribute is not provided the default value is com.mongodb.DBObject - - - - - - - - - - - Allows you to manage find* method of MongoTemplate is used to query MongoDb. Default - value for this - attribute is 'false'. This means that we'll use find(..) method thus resulting in a - Message with - payload of type List of entities identified by 'entity-class' attribute. If you - want/expect a single - value set this attribute to 'true' which will result in invocation of findOne(..) method - resulting in - the payload of type identified by 'entity-class' attribute (default - com.mongodb.DBObject) - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.mongodb.outbound.MongoDbStoringMessageHandler' that - stores a data from incoming messages to the MongoDB collection. - - - - - - - - - - - - - Specifies the order for invocation when this adapter is connected as a - subscriber to a SubscribableChannel. - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway' for - querying a MongoDb database in response to a message on the request channel. - - The response received from the database will be used to create the response - Message on the reply channel. - - - - - - - - - - - - - The Message Channel where messages will be sent in order - to query the database. - - - - - - - - - - - - The Message Channel to which the database response will be sent. - - - - - - - - - - - - - - - - - Specify whether this outbound gateway must return a non-null value. This value is - 'true' by default, and a ReplyRequiredException will be thrown when - the underlying service returns a null value. - - - - - - - - - - Specifies the order for invocation when this endpoint is connected as a - subscriber to a SubscribableChannel. - - - - - - - - - - - - - - - - String representation of a MongoDb Query (e.g., - query="{'name' : 'Bob'}"). - Please refer to MongoDb documentation for more query samples - https://www.mongodb.org/display/DOCS/Querying - This attribute is - mutually exclusive with 'query-expression' attribute. - - - - - - - SpEL expression which should resolve to a String query (please refer to the 'query' - attribute), or to an instance of MongoDb Query (e.q., - query-expression="new BasicQuery('{''name'' : ''Bob''}').limit(2)"). - - - - - - - - The fully qualified name of the entity class to be passed to - find(..) or findOne(..) method MongoTemplate. - If this attribute is not provided the default value is org.bson.Document - - - - - - - - - - - - Reference to an instance of - org.springframework.integration.mongodb.outbound.MessageCollectionCallback - with the request message context. - - - - - - - - - - - - - - - - Common configuration for mongodb adapters. - - - - - - - - Reference to an instance of - org.springframework.data.mongodb.MongoDbFactory - - - - - - - - - - - - Reference to an instance of - org.springframework.data.mongodb.core.MongoTemplate - - - - - - - - - - - Identifies the name of the MongoDb collection to - use. - This attribute is mutually exclusive with - 'collection-name-expression' - attribute. - - - - - - - SpEL expression which should resolve to a String - value identifying the - name of the MongoDb collection to use. - This - attribute is mutually exclusive with 'collection-name' attribute. - - - - - - - - - - - - Reference to an instance of - org.springframework.data.mongodb.core.convert.MongoConverter - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/MongoDbContainerTest.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/MongoDbContainerTest.java deleted file mode 100644 index 35dccc4a726..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/MongoDbContainerTest.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb; - -import java.time.Duration; - -import com.mongodb.ConnectionString; -import com.mongodb.MongoClientSettings; -import com.mongodb.MongoException; -import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoCollection; -import org.bson.Document; -import org.bson.UuidRepresentation; -import org.bson.conversions.Bson; -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.MongoDBContainer; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.dao.DataAccessException; -import org.springframework.data.annotation.Id; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; -import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.integration.mongodb.outbound.MessageCollectionCallback; -import org.springframework.messaging.Message; - -/** - * The base contract for all tests requiring a MongoDb connection. - * The Testcontainers 'reuse' option must be disabled, so, Ryuk container is started - * and will clean all the containers up from this test suite after JVM exit. - * Since the Redis container instance is shared via static property, it is going to be - * started only once per JVM, therefore the target Docker container is reused automatically. - * - * @author Oleg Zhurakousky - * @author Xavier Padro - * @author Artem Bilan - * @author David Turanski - * @author Artem Vozhdayenko - * - * @since 6.0 - */ -@Testcontainers(disabledWithoutDocker = true) -public interface MongoDbContainerTest { - - MongoDBContainer MONGO_CONTAINER = new MongoDBContainer("mongo:5.0.9"); - - @BeforeAll - static void startContainer() { - MONGO_CONTAINER.start(); - } - - static MongoDatabaseFactory createMongoDbFactory() { - return new SimpleMongoClientDatabaseFactory(MongoClients.create(getMongoClientSettings()), "test"); - } - - static ReactiveMongoDatabaseFactory createReactiveMongoDbFactory() { - return new SimpleReactiveMongoDatabaseFactory( - com.mongodb.reactivestreams.client.MongoClients.create(getMongoClientSettings()), - "test"); - } - - private static MongoClientSettings getMongoClientSettings() { - return MongoClientSettings.builder() - .applyConnectionString(new ConnectionString( - "mongodb://localhost:" + MONGO_CONTAINER.getFirstMappedPort())) - .uuidRepresentation(UuidRepresentation.STANDARD).build(); - } - - static void prepareMongoData(MongoDatabaseFactory mongoDatabaseFactory, String... additionalCollectionsToDrop) { - cleanupCollections(mongoDatabaseFactory, additionalCollectionsToDrop); - } - - static void prepareReactiveMongoData(ReactiveMongoDatabaseFactory mongoDatabaseFactory, String... additionalCollectionsToDrop) { - cleanupCollections(mongoDatabaseFactory, additionalCollectionsToDrop); - } - - static void cleanupCollections(ReactiveMongoDatabaseFactory mongoDbFactory, - String... additionalCollectionsToDrop) { - - ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoDbFactory); - template.dropCollection("messages").block(Duration.ofSeconds(3)); - template.dropCollection("configurableStoreMessages").block(Duration.ofSeconds(3)); - template.dropCollection("data").block(Duration.ofSeconds(3)); - for (String additionalCollection : additionalCollectionsToDrop) { - template.dropCollection(additionalCollection).block(Duration.ofSeconds(3)); - } - } - - static void cleanupCollections(MongoDatabaseFactory mongoDbFactory, String... additionalCollectionsToDrop) { - MongoTemplate template = new MongoTemplate(mongoDbFactory); - template.dropCollection("messages"); - template.dropCollection("configurableStoreMessages"); - template.dropCollection("data"); - for (String additionalCollection : additionalCollectionsToDrop) { - template.dropCollection(additionalCollection); - } - } - - static Person createPerson() { - Address address = new Address(); - address.setCity("Philadelphia"); - address.setStreet("2121 Rawn street"); - address.setState("PA"); - - Person person = new Person(); - person.setAddress(address); - person.setName("Oleg"); - return person; - } - - static Person createPerson(String name) { - Address address = new Address(); - address.setCity("Philadelphia"); - address.setStreet("2121 Rawn street"); - address.setState("PA"); - - Person person = new Person(); - person.setAddress(address); - person.setName(name); - return person; - } - - class Person { - - @Id - private String id; - - private Address address; - - private String name; - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - class Address { - - private String street; - - private String city; - - private String state; - - public String getStreet() { - return street; - } - - public void setStreet(String street) { - this.street = street; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - } - - class TestMongoConverter extends MappingMongoConverter { - - public TestMongoConverter( - MongoDatabaseFactory mongoDbFactory, - MappingContext, MongoPersistentProperty> mappingContext) { - - super(new DefaultDbRefResolver(mongoDbFactory), mappingContext); - } - - @Override - public void write(Object source, Bson target) { - super.write(source, target); - } - - @Override - public S read(Class clazz, Bson source) { - return super.read(clazz, source); - } - - } - - class ReactiveTestMongoConverter extends MappingMongoConverter { - - public ReactiveTestMongoConverter( - ReactiveMongoDatabaseFactory mongoDbFactory, - MappingContext, MongoPersistentProperty> mappingContext) { - - super(NoOpDbRefResolver.INSTANCE, mappingContext); - } - - @Override - public void write(Object source, Bson target) { - super.write(source, target); - } - - @Override - public S read(Class clazz, Bson source) { - return super.read(clazz, source); - } - - } - - class TestCollectionCallback implements MessageCollectionCallback { - - @Override - public Long doInCollection(MongoCollection collection, Message message) - throws MongoException, DataAccessException { - - return collection.countDocuments(); - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/Person.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/Person.java deleted file mode 100644 index 4dee0de9e13..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/Person.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb; - -import org.bson.types.ObjectId; - -/** - * @author Christoph Strobl - * @author Artem Bilan - * - * @since 5.3 - */ -public class Person { - - private ObjectId id; - - private String firstName; - - private int age; - - private Person friend; - - public Person() { - this.id = new ObjectId(); - } - - @Override - public String toString() { - return "Person [id=" + this.id + ", firstName=" + this.firstName + ", age=" + this.age + - ", friend=" + this.friend + "]"; - } - - public Person(ObjectId id, String firstName) { - this.id = id; - this.firstName = firstName; - } - - public Person(String firstName, int age) { - this(); - this.firstName = firstName; - this.age = age; - } - - public Person(String firstName) { - this(); - this.firstName = firstName; - } - - public ObjectId getId() { - return this.id; - } - - public String getFirstName() { - return this.firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public int getAge() { - return this.age; - } - - public void setAge(int age) { - this.age = age; - } - - public Person getFriend() { - return this.friend; - } - - public void setFriend(Person friend) { - this.friend = friend; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (obj == null) { - return false; - } - - if (!(getClass().equals(obj.getClass()))) { - return false; - } - - Person that = (Person) obj; - - return this.id != null && this.id.equals(that.id); - } - - @Override - public int hashCode() { - return this.id.hashCode(); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml deleted file mode 100644 index 58f72d3e926..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests.java deleted file mode 100644 index 69505b786ce..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import java.util.List; - -import com.mongodb.BasicDBObject; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.integration.aop.ReceiveMessageAdvice; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.messaging.Message; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Yaron Yamin - * @author Artem Vozhdayenko - * - * @since 2.2 - */ -@SpringJUnitConfig -@DirtiesContext -class MongoDbInboundChannelAdapterIntegrationTests implements MongoDbContainerTest { - - @Autowired - private MongoTemplate mongoTemplate; - - @Autowired - private QueueChannel replyChannel; - - @Autowired - private QueueChannel afterCommitChannel; - - @Autowired - @Qualifier("mongoInboundAdapter") - private SourcePollingChannelAdapter mongoInboundAdapter; - - @Autowired - @Qualifier("mongoInboundAdapterNamedFactory") - private SourcePollingChannelAdapter mongoInboundAdapterNamedFactory; - - @Autowired - @Qualifier("mongoInboundAdapterWithTemplate") - private SourcePollingChannelAdapter mongoInboundAdapterWithTemplate; - - @Autowired - @Qualifier("mongoInboundAdapterWithNamedCollection") - private SourcePollingChannelAdapter mongoInboundAdapterWithNamedCollection; - - @Autowired - @Qualifier("mongoInboundAdapterWithQueryExpression") - private SourcePollingChannelAdapter mongoInboundAdapterWithQueryExpression; - - @Autowired - @Qualifier("mongoInboundAdapterWithStringQueryExpression") - private SourcePollingChannelAdapter mongoInboundAdapterWithStringQueryExpression; - - @Autowired - @Qualifier("mongoInboundAdapterWithNamedCollectionExpression") - private SourcePollingChannelAdapter mongoInboundAdapterWithNamedCollectionExpression; - - @Autowired - @Qualifier("inboundAdapterWithOnSuccessDisposition") - private SourcePollingChannelAdapter inboundAdapterWithOnSuccessDisposition; - - @Autowired - @Qualifier("mongoInboundAdapterWithConverter") - private SourcePollingChannelAdapter mongoInboundAdapterWithConverter; - - @Test - void testWithDefaultMongoFactory() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "data"); - - this.mongoInboundAdapter.start(); - - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().get(0).getName()).isEqualTo("Bob"); - assertThat(this.replyChannel.receive(10000)).isNotNull(); - - this.mongoInboundAdapter.stop(); - this.replyChannel.purge(null); - } - - @Test - void testWithNamedMongoFactory() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "data"); - - this.mongoInboundAdapterNamedFactory.start(); - - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().get(0)).containsEntry("name", "Bob"); - - this.mongoInboundAdapterNamedFactory.stop(); - this.replyChannel.purge(null); - } - - @Test - void testWithMongoTemplate() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "data"); - - this.mongoInboundAdapterWithTemplate.start(); - - @SuppressWarnings("unchecked") - Message message = (Message) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().getName()).isEqualTo("Bob"); - - this.mongoInboundAdapterWithTemplate.stop(); - this.replyChannel.purge(null); - } - - @Test - void testWithNamedCollection() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "foo"); - - this.mongoInboundAdapterWithNamedCollection.start(); - - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().get(0).getName()).isEqualTo("Bob"); - - this.mongoInboundAdapterWithNamedCollection.stop(); - this.replyChannel.purge(null); - } - - @Test - void testWithQueryExpression() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "foo"); - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "foo"); - this.mongoInboundAdapterWithQueryExpression.start(); - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).hasSize(1); - assertThat(message.getPayload().get(0).getName()).isEqualTo("Bob"); - this.mongoInboundAdapterWithQueryExpression.stop(); - } - - @Test - void testWithStringQueryExpression() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "foo"); - this.mongoInboundAdapterWithStringQueryExpression.start(); - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().get(0).getName()).isEqualTo("Bob"); - this.mongoInboundAdapterWithStringQueryExpression.stop(); - } - - @Test - void testWithNamedCollectionExpression() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "foo"); - - this.mongoInboundAdapterWithNamedCollectionExpression.start(); - - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().get(0).getName()).isEqualTo("Bob"); - - this.mongoInboundAdapterWithNamedCollectionExpression.stop(); - this.replyChannel.purge(null); - } - - @Test - void testWithOnSuccessDisposition() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "data"); - - this.inboundAdapterWithOnSuccessDisposition.start(); - - assertThat(replyChannel.receive(10000)).isNotNull(); - assertThat(replyChannel.receive(100)).isNull(); - - assertThat(this.afterCommitChannel.receive(10000)).isNotNull(); - - assertThat(this.mongoTemplate.findOne(new Query(Criteria.where("name").is("Bob")), Person.class, "data")) - .isNull(); - this.inboundAdapterWithOnSuccessDisposition.stop(); - this.replyChannel.purge(null); - } - - @Test - void testWithMongoConverter() { - this.mongoTemplate.save(MongoDbContainerTest.createPerson("Bob"), "data"); - - this.mongoInboundAdapterWithConverter.start(); - - @SuppressWarnings("unchecked") - Message> message = (Message>) replyChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().get(0).getName()).isEqualTo("Bob"); - assertThat(replyChannel.receive(10000)).isNotNull(); - - this.mongoInboundAdapterWithConverter.stop(); - this.replyChannel.purge(null); - } - - @Test - void testFailureWithQueryAndQueryExpression() { - assertThatThrownBy(() -> new ClassPathXmlApplicationContext("inbound-fail-q-qex.xml", MongoDbInboundChannelAdapterIntegrationTests.class)) - .isInstanceOf(BeanDefinitionParsingException.class); - } - - @Test - void testFailureWithFactoryAndTemplate() { - assertThatThrownBy(() -> new ClassPathXmlApplicationContext("inbound-fail-factory-template.xml", MongoDbInboundChannelAdapterIntegrationTests.class)) - .isInstanceOf(BeanDefinitionParsingException.class); - } - - @Test - void testFailureWithCollectionAndCollectionExpression() { - assertThatThrownBy(() -> new ClassPathXmlApplicationContext("inbound-fail-c-cex.xml", MongoDbInboundChannelAdapterIntegrationTests.class)) - .isInstanceOf(BeanDefinitionParsingException.class); - } - - @Test - void testFailureWithTemplateAndConverter() { - assertThatThrownBy(() -> new ClassPathXmlApplicationContext("inbound-fail-converter-template.xml", MongoDbInboundChannelAdapterIntegrationTests.class)) - .isInstanceOf(BeanDefinitionParsingException.class); - } - - public static class DocumentCleaner { - - public void remove(MongoOperations mongoOperations, Object target, String collectionName) { - if (target instanceof List) { - List documents = (List) target; - for (Object document : documents) { - mongoOperations.remove(document, collectionName); - } - } - } - - } - - public static final class TestMessageSourceAdvice implements ReceiveMessageAdvice { - - @Override - public Message afterReceive(Message result, Object source) { - return result; - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml deleted file mode 100644 index 416434a1a45..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests.java deleted file mode 100644 index 264f619186c..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.mongodb.inbound.MongoDbMessageSource; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Yaron Yamin - */ -@SpringJUnitConfig -@DirtiesContext -public class MongoDbInboundChannelAdapterParserTests { - - @Autowired - private MongoDatabaseFactory mongoDbFactory; - - @Autowired - private MongoConverter mongoConverter; - - @Autowired - private MongoTemplate mongoDbTemplate; - - @Autowired - @Qualifier("minimalConfig.adapter") - private SourcePollingChannelAdapter minimalConfigAdapter; - - @Autowired - @Qualifier("fullConfigWithCollectionExpression.adapter") - private SourcePollingChannelAdapter fullConfigWithCollectionExpressionAdapter; - - @Autowired - @Qualifier("fullConfigWithQueryExpression.adapter") - private SourcePollingChannelAdapter fullConfigWithQueryExpressionAdapter; - - @Autowired - @Qualifier("fullConfigWithQuery.adapter") - private SourcePollingChannelAdapter fullConfigWithQueryAdapter; - - @Autowired - @Qualifier("fullConfigWithSpelQuery.adapter") - private SourcePollingChannelAdapter fullConfigWithSpelQueryAdapter; - - @Autowired - @Qualifier("fullConfigWithCollectionName.adapter") - private SourcePollingChannelAdapter fullConfigWithCollectionNameAdapter; - - @Autowired - @Qualifier("fullConfigWithMongoTemplate.adapter") - private SourcePollingChannelAdapter fullConfigWithMongoTemplateAdapter; - - @Test - public void minimalConfig() { - MongoDbMessageSource source = TestUtils.getPropertyValue(this.minimalConfigAdapter, "source", - MongoDbMessageSource.class); - - assertThat(TestUtils.getPropertyValue(this.minimalConfigAdapter, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(source, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(source, "mongoDbFactory")).isEqualTo(this.mongoDbFactory); - assertThat(TestUtils.getPropertyValue(source, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression") instanceof LiteralExpression) - .isTrue(); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression.literalValue")).isEqualTo("data"); - } - - @Test - public void fullConfigWithCollectionExpression() { - MongoDbMessageSource source = assertMongoDbMessageSource(this.fullConfigWithCollectionExpressionAdapter); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression") instanceof SpelExpression).isTrue(); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression.expression")).isEqualTo("'foo'"); - } - - @Test - public void fullConfigWithQueryExpression() { - MongoDbMessageSource source = assertMongoDbMessageSource(this.fullConfigWithQueryExpressionAdapter); - assertThat(TestUtils.getPropertyValue(source, "queryExpression") instanceof SpelExpression).isTrue(); - assertThat(TestUtils.getPropertyValue(source, "queryExpression.expression")) - .isEqualTo("new BasicQuery('{''address.state'' : ''PA''}').limit(2)"); - assertThat(TestUtils.getPropertyValue(source, "updateExpression.literalValue")) - .isEqualTo("{ $set: {'address.state' : 'NJ'} }"); - } - - @Test - public void fullConfigWithSpelQuery() { - MongoDbMessageSource source = assertMongoDbMessageSource(this.fullConfigWithSpelQueryAdapter); - assertThat(TestUtils.getPropertyValue(source, "queryExpression") instanceof LiteralExpression).isTrue(); - assertThat(TestUtils.getPropertyValue(source, "queryExpression.literalValue")) - .isEqualTo("{''address.state'' : ''PA''}"); - } - - @Test - public void fullConfigWithQuery() { - MongoDbMessageSource source = assertMongoDbMessageSource(this.fullConfigWithQueryAdapter); - assertThat(TestUtils.getPropertyValue(source, "queryExpression") instanceof LiteralExpression).isTrue(); - assertThat(TestUtils.getPropertyValue(source, "queryExpression.literalValue")) - .isEqualTo("{'address.state' : 'PA'}"); - } - - @Test - public void fullConfigWithCollectionName() { - MongoDbMessageSource source = assertMongoDbMessageSource(this.fullConfigWithCollectionNameAdapter); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression") instanceof LiteralExpression) - .isTrue(); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression.literalValue")).isEqualTo("foo"); - } - - @Test - public void fullConfigWithMongoTemplate() { - MongoDbMessageSource source = TestUtils.getPropertyValue(this.fullConfigWithMongoTemplateAdapter, "source", - MongoDbMessageSource.class); - - assertThat(TestUtils.getPropertyValue(this.fullConfigWithMongoTemplateAdapter, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(source, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(source, "mongoTemplate")).isSameAs(this.mongoDbTemplate); - assertThat(TestUtils.getPropertyValue(source, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression") instanceof LiteralExpression) - .isTrue(); - assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression.literalValue")).isEqualTo("foo"); - } - - @Test - public void templateAndFactoryFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("inbound-adapter-parser-fail-template-factory-config.xml", - getClass())); - } - - @Test - public void templateAndConverterFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("inbound-adapter-parser-fail-template-converter-config.xml", - getClass())); - } - - private MongoDbMessageSource assertMongoDbMessageSource(Object testedBean) { - MongoDbMessageSource source = TestUtils.getPropertyValue(testedBean, "source", MongoDbMessageSource.class); - - assertThat(TestUtils.getPropertyValue(testedBean, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(source, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(source, "mongoDbFactory")).isEqualTo(this.mongoDbFactory); - assertThat(TestUtils.getPropertyValue(source, "mongoConverter")).isEqualTo(this.mongoConverter); - assertThat(TestUtils.getPropertyValue(source, "evaluationContext")).isNotNull(); - return source; - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterIntegrationTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterIntegrationTests.java deleted file mode 100644 index 48842d97b1e..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterIntegrationTests.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import com.mongodb.BasicDBObject; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Artem Vozhdayenko - */ -class MongoDbOutboundChannelAdapterIntegrationTests implements MongoDbContainerTest { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - @Test - void testWithDefaultMongoFactory() throws Exception { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("outbound-adapter-config.xml", this.getClass()); - - MessageChannel channel = context.getBean("simpleAdapter", MessageChannel.class); - Message message = new GenericMessage(MongoDbContainerTest.createPerson("Bob")); - channel.send(message); - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - assertThat(template.find(new BasicQuery("{'name' : 'Bob'}"), Person.class, "data")).isNotNull(); - context.close(); - } - - @Test - void testWithNamedCollection() throws Exception { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, "foo"); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("outbound-adapter-config.xml", this.getClass()); - - MessageChannel channel = context.getBean("simpleAdapterWithNamedCollection", MessageChannel.class); - Message message = - MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")) - .setHeader("collectionName", "foo") - .build(); - channel.send(message); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - assertThat(template.find(new BasicQuery("{'name' : 'Bob'}"), Person.class, "foo")).isNotNull(); - context.close(); - } - - @Test - void testWithTemplate() throws Exception { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, "foo"); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("outbound-adapter-config.xml", this.getClass()); - - MessageChannel channel = context.getBean("simpleAdapterWithTemplate", MessageChannel.class); - Message message = - MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")) - .setHeader("collectionName", "foo") - .build(); - - channel.send(message); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - assertThat(template.find(new BasicQuery("{'name' : 'Bob'}"), Person.class, "foo")).isNotNull(); - context.close(); - } - - @Test - void testSavingDbObject() throws Exception { - - BasicDBObject dbObject = BasicDBObject.parse("{'foo' : 'bar'}"); - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, "foo"); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("outbound-adapter-config.xml", this.getClass()); - - MessageChannel channel = context.getBean("simpleAdapterWithTemplate", MessageChannel.class); - - Message message = - MessageBuilder.withPayload(dbObject) - .setHeader("collectionName", "foo") - .build(); - - channel.send(message); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - assertThat(template.find(new BasicQuery("{'foo' : 'bar'}"), BasicDBObject.class, "foo")).isNotNull(); - context.close(); - } - - @Test - void testSavingJSONString() throws Exception { - - String object = "{'foo' : 'bar'}"; - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, "foo"); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("outbound-adapter-config.xml", this.getClass()); - - MessageChannel channel = context.getBean("simpleAdapterWithTemplate", MessageChannel.class); - - Message message = - MessageBuilder.withPayload(object) - .setHeader("collectionName", "foo") - .build(); - - channel.send(message); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - assertThat(template.find(new BasicQuery("{'foo' : 'bar'}"), BasicDBObject.class, "foo")).isNotNull(); - context.close(); - } - - @Test - void testWithMongoConverter() throws Exception { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("outbound-adapter-config.xml", this.getClass()); - - MessageChannel channel = context.getBean("simpleAdapterWithConverter", MessageChannel.class); - Message message = new GenericMessage(MongoDbContainerTest.createPerson("Bob")); - channel.send(message); - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - assertThat(template.find(new BasicQuery("{'name' : 'Bob'}"), Person.class, "data")).isNotNull(); - context.close(); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParserTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index a63946b3f97..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParserTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParserTests.java deleted file mode 100644 index a0bbb3ae499..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.endpoint.PollingConsumer; -import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; -import org.springframework.integration.mongodb.outbound.MongoDbStoringMessageHandler; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Gary Russell - */ -@SpringJUnitConfig -public class MongoDbOutboundChannelAdapterParserTests { - - @Autowired - ApplicationContext context; - - @Test - public void minimalConfig() { - MongoDbStoringMessageHandler handler = - TestUtils.getPropertyValue(context.getBean("minimalConfig.adapter"), "handler", - MongoDbStoringMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "componentName")).isEqualTo("minimalConfig.adapter"); - assertThat(TestUtils.getPropertyValue(handler, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(handler, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "mongoDbFactory")).isEqualTo(context.getBean("mongoDbFactory")); - assertThat(TestUtils.getPropertyValue(handler, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression")).isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression.literalValue")).isEqualTo("data"); - } - - @Test - public void fullConfigWithCollectionExpression() { - MongoDbStoringMessageHandler handler = - TestUtils.getPropertyValue(context.getBean("fullConfigWithCollectionExpression.adapter"), "handler", - MongoDbStoringMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "componentName")) - .isEqualTo("fullConfigWithCollectionExpression.adapter"); - assertThat(TestUtils.getPropertyValue(handler, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(handler, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "mongoDbFactory")).isEqualTo(context.getBean("mongoDbFactory")); - assertThat(TestUtils.getPropertyValue(handler, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression")).isInstanceOf(SpelExpression.class); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression.expression")) - .isEqualTo("headers.collectionName"); - } - - @Test - public void fullConfigWithCollection() { - MongoDbStoringMessageHandler handler = - TestUtils.getPropertyValue(context.getBean("fullConfigWithCollection.adapter"), "handler", - MongoDbStoringMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "componentName")).isEqualTo("fullConfigWithCollection.adapter"); - assertThat(TestUtils.getPropertyValue(handler, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(handler, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "mongoDbFactory")).isEqualTo(context.getBean("mongoDbFactory")); - assertThat(TestUtils.getPropertyValue(handler, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression")).isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression.literalValue")).isEqualTo("foo"); - } - - @Test - public void fullConfigWithMongoTemplate() { - MongoDbStoringMessageHandler handler = - TestUtils.getPropertyValue(context.getBean("fullConfigWithMongoTemplate.adapter"), "handler", - MongoDbStoringMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "componentName")) - .isEqualTo("fullConfigWithMongoTemplate.adapter"); - assertThat(TestUtils.getPropertyValue(handler, "shouldTrack")).isEqualTo(false); - assertThat(TestUtils.getPropertyValue(handler, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression")).isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(handler, "collectionNameExpression.literalValue")).isEqualTo("foo"); - } - - @Test - public void templateAndFactoryFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("outbound-adapter-parser-fail-template-factory-config.xml", - getClass())); - } - - @Test - public void templateAndConverterFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("outbound-adapter-parser-fail-template-converter-config.xml", - getClass())); - } - - @Test - public void testInt3024PollerAndRequestHandlerAdviceChain() { - AbstractEndpoint endpoint = context.getBean("pollableAdapter", AbstractEndpoint.class); - assertThat(endpoint).isInstanceOf(PollingConsumer.class); - MessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class); - assertThat(AopUtils.isAopProxy(handler)).isTrue(); - assertThat(((Advised) handler).getAdvisors()[0].getAdvice()).isInstanceOf(RequestHandlerRetryAdvice.class); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml deleted file mode 100644 index d6313b58068..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java deleted file mode 100644 index 4a6690caa8c..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.config; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.endpoint.PollingConsumer; -import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; -import org.springframework.integration.mongodb.outbound.MessageCollectionCallback; -import org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Xavier Padro - * @author Artem Bilan - * - * @since 5.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class MongoDbOutboundGatewayParserTests { - - @Autowired - private ApplicationContext context; - - @Autowired - private MongoDatabaseFactory mongoDbFactory; - - @Autowired - private MongoConverter mongoConverter; - - @Test - public void minimalConfig() { - AbstractEndpoint endpoint = this.context.getBean("minimalConfig", AbstractEndpoint.class); - MongoDbOutboundGateway gateway = - TestUtils.getPropertyValue(endpoint, "handler", MongoDbOutboundGateway.class); - - assertThat(TestUtils.getPropertyValue(gateway, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "mongoDbFactory")).isSameAs(this.mongoDbFactory); - assertThat(TestUtils.getPropertyValue(gateway, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression")) - .isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout")).isEqualTo(30000L); - - assertThat(endpoint).isInstanceOf(PollingConsumer.class); - MessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class); - List advices = TestUtils.getPropertyValue(handler, "adviceChain", List.class); - assertThat(advices.get(0)).isInstanceOf(RequestHandlerRetryAdvice.class); - } - - @Test - public void fullConfigWithCollectionExpression() { - MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( - context.getBean("fullConfigWithCollectionExpression"), "handler", MongoDbOutboundGateway.class); - - assertThat(TestUtils.getPropertyValue(gateway, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "mongoDbFactory")).isSameAs(this.mongoDbFactory); - assertThat(TestUtils.getPropertyValue(gateway, "mongoConverter")).isSameAs(this.mongoConverter); - assertThat(TestUtils.getPropertyValue(gateway, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression")).isInstanceOf(SpelExpression.class); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression.expression")) - .isEqualTo("headers.collectionName"); - } - - @Test - public void fullConfigWithCollection() { - MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( - context.getBean("fullConfigWithCollection"), "handler", MongoDbOutboundGateway.class); - - assertThat(TestUtils.getPropertyValue(gateway, "mongoTemplate")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "mongoDbFactory")).isSameAs(this.mongoDbFactory); - assertThat(TestUtils.getPropertyValue(gateway, "mongoConverter")).isSameAs(this.mongoConverter); - assertThat(TestUtils.getPropertyValue(gateway, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression")) - .isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")).isEqualTo("foo"); - } - - @Test - public void fullConfigWithMongoTemplate() { - MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( - context.getBean("fullConfigWithTemplate"), "handler", MongoDbOutboundGateway.class); - - assertThat(TestUtils.getPropertyValue(gateway, "mongoTemplate")).isSameAs(context.getBean("mongoDbTemplate")); - assertThat(TestUtils.getPropertyValue(gateway, "mongoDbFactory")).isNull(); - assertThat(TestUtils.getPropertyValue(gateway, "mongoConverter")).isNull(); - assertThat(TestUtils.getPropertyValue(gateway, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression")) - .isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")).isEqualTo("foo"); - } - - @Test - public void fullConfigWithMongoDbCollectionCallback() { - MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( - context.getBean("fullConfigWithMongoDbCollectionCallback"), "handler", MongoDbOutboundGateway.class); - - assertThat(TestUtils.getPropertyValue(gateway, "mongoTemplate")).isSameAs(context.getBean("mongoDbTemplate")); - assertThat(TestUtils.getPropertyValue(gateway, "mongoDbFactory")).isNull(); - assertThat(TestUtils.getPropertyValue(gateway, "mongoConverter")).isNull(); - assertThat(TestUtils.getPropertyValue(gateway, "evaluationContext")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression")) - .isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(gateway, "collectionCallback")) - .isInstanceOf(MessageCollectionCallback.class); - } - - @Test - public void templateAndFactoryFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("outbound-gateway-fail-template-factory-config.xml", - getClass())); - } - - @Test - public void templateAndConverterFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("outbound-gateway-fail-template-converter-config.xml", - this.getClass())); - } - - @Test - public void collectionCallbackAndQueryFail() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("outbound-gateway-fail-collection-callback-config.xml", - this.getClass())); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-adapter-parser-fail-template-converter-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-adapter-parser-fail-template-converter-config.xml deleted file mode 100644 index 11598ed02d9..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-adapter-parser-fail-template-converter-config.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-adapter-parser-fail-template-factory-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-adapter-parser-fail-template-factory-config.xml deleted file mode 100644 index de35510c734..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-adapter-parser-fail-template-factory-config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-c-cex.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-c-cex.xml deleted file mode 100644 index f18d48dae60..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-c-cex.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-converter-template.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-converter-template.xml deleted file mode 100644 index 755ada6b35c..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-converter-template.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-factory-template.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-factory-template.xml deleted file mode 100644 index 20bbe86bc04..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-factory-template.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-q-qex.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-q-qex.xml deleted file mode 100644 index 1c20d4a03ac..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/inbound-fail-q-qex.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-config.xml deleted file mode 100644 index 74c4b332271..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-config.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-parser-fail-template-converter-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-parser-fail-template-converter-config.xml deleted file mode 100644 index 2127bea4043..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-parser-fail-template-converter-config.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-parser-fail-template-factory-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-parser-fail-template-factory-config.xml deleted file mode 100644 index 60305da05b2..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-adapter-parser-fail-template-factory-config.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-collection-callback-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-collection-callback-config.xml deleted file mode 100644 index 54a0aa25666..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-collection-callback-config.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml deleted file mode 100644 index 81973a86db2..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml deleted file mode 100644 index 2319198e881..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java deleted file mode 100644 index 36038a3e8ed..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.dsl; - -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.BulkOperations; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.MessageChannels; -import org.springframework.integration.dsl.QueueChannelSpec; -import org.springframework.integration.handler.ReplyRequiredException; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.mongodb.outbound.MessageCollectionCallback; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.awaitility.Awaitility.await; - -/** - * @author Xavier Padro - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -@SpringJUnitConfig -@DirtiesContext -class MongoDbTests implements MongoDbContainerTest { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - REACTIVE_MONGO_DATABASE_FACTORY = MongoDbContainerTest.createReactiveMongoDbFactory(); - } - - static ReactiveMongoDatabaseFactory REACTIVE_MONGO_DATABASE_FACTORY; - - private static final String COLLECTION_NAME = "data"; - - @Autowired - private PollableChannel getResultChannel; - - @Autowired - @Qualifier("gatewaySingleQueryFlow.input") - private MessageChannel gatewaySingleQueryFlow; - - @Autowired - @Qualifier("gatewaySingleQueryWithTemplateFlow.input") - private MessageChannel gatewaySingleQueryWithTemplateFlow; - - @Autowired - @Qualifier("gatewaySingleQueryExpressionFlow.input") - private MessageChannel gatewaySingleQueryExpressionFlow; - - @Autowired - @Qualifier("gatewayQueryExpressionFlow.input") - private MessageChannel gatewayQueryExpressionFlow; - - @Autowired - @Qualifier("gatewayQueryExpressionLimitFlow.input") - private MessageChannel gatewayQueryExpressionLimitFlow; - - @Autowired - @Qualifier("gatewayQueryFunctionFlow.input") - private MessageChannel gatewayQueryFunctionFlow; - - @Autowired - @Qualifier("gatewayCollectionNameFunctionFlow.input") - private MessageChannel gatewayCollectionNameFunctionFlow; - - @Autowired - @Qualifier("gatewayCollectionCallbackFlow.input") - private MessageChannel gatewayCollectionCallbackFlow; - - @Autowired - private MongoOperations mongoTemplate; - - @BeforeEach - public void setUp() { - createPersons(); - } - - @AfterEach - public void cleanUp() { - mongoTemplate.dropCollection(COLLECTION_NAME); - } - - @Test - void testGatewayWithSingleQuery() { - gatewaySingleQueryFlow.send(MessageBuilder - .withPayload("Xavi") - .setHeader("collection", "data") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - Person retrievedPerson = (Person) result.getPayload(); - assertThat(retrievedPerson.getName()).isEqualTo("Xavi"); - } - - @Test - void testGatewayWithSingleQueryWithTemplate() { - gatewaySingleQueryWithTemplateFlow.send(MessageBuilder.withPayload("Xavi").build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - Person retrievedPerson = (Person) result.getPayload(); - assertThat(retrievedPerson.getName()).isEqualTo("Xavi"); - } - - @Test - void testGatewayWithSingleQueryExpression() { - gatewaySingleQueryExpressionFlow.send(MessageBuilder - .withPayload("") - .setHeader("query", "{'name' : 'Artem'}") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - Person retrievedPerson = (Person) result.getPayload(); - assertThat(retrievedPerson.getName()).isEqualTo("Artem"); - } - - @Test - void testGatewayWithSingleQueryExpressionNoPersonFound() { - assertThatThrownBy(() -> gatewaySingleQueryExpressionFlow.send(MessageBuilder - .withPayload("") - .setHeader("query", "{'name' : 'NonExisting'}") - .build())) - .isInstanceOf(ReplyRequiredException.class); - } - - @Test - void testGatewayWithQueryExpression() { - gatewayQueryExpressionFlow.send(MessageBuilder - .withPayload("") - .setHeader("query", "{}") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - List retrievedPersons = getPersons(result); - assertThat(retrievedPersons).hasSize(4); - } - - @Test - void testGatewayWithQueryExpressionAndLimit() { - gatewayQueryExpressionLimitFlow.send(MessageBuilder - .withPayload("") - .setHeader("query", "{}") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - List retrievedPersons = getPersons(result); - assertThat(retrievedPersons).hasSize(2); - } - - @Test - void testGatewayWithQueryFunction() { - gatewayQueryFunctionFlow.send(MessageBuilder - .withPayload("Gary") - .setHeader("collection", "data") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - Person person = (Person) result.getPayload(); - assertThat(person.getName()).isEqualTo("Gary"); - } - - @Test - void testGatewayWithCollectionNameFunction() { - gatewayCollectionNameFunctionFlow.send(MessageBuilder - .withPayload("data") - .setHeader("query", "{'name' : 'Gary'}") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - Person person = (Person) result.getPayload(); - assertThat(person.getName()).isEqualTo("Gary"); - } - - @Test - void testGatewayWithCollectionCallback() { - gatewayCollectionCallbackFlow.send(MessageBuilder - .withPayload("") - .build()); - - Message result = this.getResultChannel.receive(10_000); - - assertThat(result).isNotNull(); - long count = (Long) result.getPayload(); - assertThat(count).isEqualTo(4); - } - - @SuppressWarnings("unchecked") - private List getPersons(Message message) { - return (List) message.getPayload(); - } - - private void createPersons() { - BulkOperations bulkOperations = this.mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, COLLECTION_NAME); - bulkOperations.insert(Arrays.asList( - MongoDbContainerTest.createPerson("Artem"), - MongoDbContainerTest.createPerson("Gary"), - MongoDbContainerTest.createPerson("Oleg"), - MongoDbContainerTest.createPerson("Xavi"))); - bulkOperations.execute(); - } - - @Autowired - @Qualifier("reactiveStore.input") - private MessageChannel reactiveStoreInput; - - @Test - void testReactiveMongoDbMessageHandler() { - this.reactiveStoreInput.send(MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build()); - - ReactiveMongoTemplate reactiveMongoTemplate = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - - await().untilAsserted(() -> - assertThat( - reactiveMongoTemplate.findOne(new BasicQuery("{'name' : 'Bob'}"), Person.class, "data") - .block(Duration.ofSeconds(10))) - .isNotNull() - .extracting("name", "address.state").contains("Bob", "PA")); - } - - @Configuration - @EnableIntegration - public static class ContextConfiguration { - - @Bean - public IntegrationFlow gatewaySingleQueryFlow() { - return f -> f - .handle(queryOutboundGateway("{name: 'Xavi'}", true)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewaySingleQueryWithTemplateFlow() { - return f -> f - .handle(queryOutboundGatewayWithTemplate("{name: 'Xavi'}", true)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewaySingleQueryExpressionFlow() { - return f -> f - .handle(queryExpressionOutboundGateway(true)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewayQueryExpressionFlow() { - return f -> f - .handle(queryExpressionOutboundGateway(false)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewayQueryExpressionLimitFlow() { - return f -> f - .handle(queryExpressionOutboundGateway(false, 2)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewayQueryFunctionFlow() { - return f -> f - .handle(queryFunctionOutboundGateway(true)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewayCollectionNameFunctionFlow() { - return f -> f - .handle(collectionNameFunctionOutboundGateway(true)) - .channel(getResultChannel()); - } - - @Bean - public IntegrationFlow gatewayCollectionCallbackFlow(QueueChannel getResultChannel) { - return f -> f - .handle(collectionCallbackOutboundGateway( - (collection, requestMessage) -> collection.countDocuments())) - .channel(getResultChannel); - } - - @Bean - public QueueChannelSpec getResultChannel() { - return MessageChannels.queue(); - } - - @Bean - public MongoDatabaseFactory mongoDbFactory() { - return MONGO_DATABASE_FACTORY; - } - - @Bean - public MongoConverter mongoConverter() { - return new TestMongoConverter(mongoDbFactory(), new MongoMappingContext()); - } - - @Bean - public MongoOperations mongoTemplate() { - return new MongoTemplate(mongoDbFactory()); - } - - private MongoDbOutboundGatewaySpec queryOutboundGateway(String query, boolean expectSingleResult) { - return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) - .query(query) - .collectionNameExpression("headers.collection") - .expectSingleResult(expectSingleResult) - .entityClass(Person.class); - } - - private MongoDbOutboundGatewaySpec queryOutboundGatewayWithTemplate(String query, boolean expectSingleResult) { - return MongoDb.outboundGateway(mongoTemplate()) - .query(query) - .collectionName(COLLECTION_NAME) - .expectSingleResult(expectSingleResult) - .entityClass(Person.class); - } - - private MongoDbOutboundGatewaySpec queryExpressionOutboundGateway(boolean expectSingleResult) { - return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) - .queryExpression("headers.query") - .collectionName(COLLECTION_NAME) - .expectSingleResult(expectSingleResult) - .entityClass(Person.class); - } - - private MongoDbOutboundGatewaySpec queryExpressionOutboundGateway(boolean expectSingleResult, int maxResults) { - return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) - .queryExpression("new BasicQuery('{''address.state'' : ''PA''}').limit(" + maxResults + ")") - .collectionName(COLLECTION_NAME) - .expectSingleResult(expectSingleResult) - .entityClass(Person.class); - } - - private MongoDbOutboundGatewaySpec queryFunctionOutboundGateway(boolean expectSingleResult) { - return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) - .queryFunction(msg -> - Query.query(Criteria.where("name") - .is(msg.getPayload()))) - .collectionNameExpression("headers.collection") - .expectSingleResult(expectSingleResult) - .entityClass(Person.class); - } - - private MongoDbOutboundGatewaySpec collectionNameFunctionOutboundGateway(boolean expectSingleResult) { - return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) - .queryExpression("headers.query") - .collectionNameFunction(Message::getPayload) - .expectSingleResult(expectSingleResult) - .entityClass(Person.class); - } - - private MongoDbOutboundGatewaySpec collectionCallbackOutboundGateway( - MessageCollectionCallback collectionCallback) { - - return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) - .collectionCallback(collectionCallback) - .collectionName(COLLECTION_NAME) - .entityClass(Person.class); - } - - @Bean - public IntegrationFlow reactiveStore() { - return f -> f - .channel(MessageChannels.flux()) - .handleReactive(MongoDb.reactiveOutboundChannelAdapter(REACTIVE_MONGO_DATABASE_FACTORY)); - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbChangeStreamMessageProducerTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbChangeStreamMessageProducerTests.java deleted file mode 100644 index f61a33716b8..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbChangeStreamMessageProducerTests.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import java.time.Duration; - -import com.mongodb.client.model.changestream.OperationType; -import com.mongodb.reactivestreams.client.MongoClient; -import com.mongodb.reactivestreams.client.MongoClients; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.core.ChangeStreamEvent; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.MessageChannels; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.mongodb.Person; -import org.springframework.integration.mongodb.dsl.MongoDb; -import org.springframework.integration.mongodb.support.MongoHeaders; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.3 - */ -@Disabled("Requires MongoDb server of version 4.x started with 'replSet' option.") -@SpringJUnitConfig -@DirtiesContext -public class MongoDbChangeStreamMessageProducerTests { - - private static final String CONNECTION_STRING = - "mongodb://127.0.0.1:27017/?replicaSet=rs0&w=majority&uuidrepresentation=javaLegacy"; - - private static MongoClient mongoClient; - - @Autowired - ReactiveMongoOperations mongoTemplate; - - @Autowired - FluxMessageChannel fluxMessageChannel; - - @Autowired - MessageProducerSupport changeStreamMessageProducer; - - @BeforeAll - static void setup() { - mongoClient = MongoClients.create(CONNECTION_STRING); - } - - @AfterAll - static void tearDown() { - mongoClient.close(); - } - - @BeforeEach - void prepare() { - this.mongoTemplate.remove(Person.class).all() - .then() - .onErrorResume(ex -> this.mongoTemplate.createCollection(Person.class).then()) - .block(Duration.ofSeconds(10)); - } - - @AfterEach - void cleanUp() { - this.mongoTemplate.remove(Person.class).all() - .block(Duration.ofSeconds(10)); - } - - @Test - void testChangeStreamMessageProducer() { - Person person1 = new Person("John", 38); - Person person2 = new Person("Josh", 39); - Person person3 = new Person("Jack", 37); - - this.changeStreamMessageProducer.start(); - - StepVerifier stepVerifier = - Flux.from(this.fluxMessageChannel) - .as(StepVerifier::create) - .assertNext((message) -> { - assertThat(message.getPayload()) - .isInstanceOf(ChangeStreamEvent.class) - .extracting("body") - .asInstanceOf(InstanceOfAssertFactories.type(Person.class)) - .isEqualTo(person1); - assertThat(message.getHeaders()) - .containsEntry(MongoHeaders.COLLECTION_NAME, "person") - .containsEntry(MongoHeaders.CHANGE_STREAM_OPERATION_TYPE, OperationType.INSERT) - .containsKeys(MongoHeaders.CHANGE_STREAM_TIMESTAMP, - MongoHeaders.CHANGE_STREAM_RESUME_TOKEN); - }) - .assertNext((message) -> - assertThat(message.getPayload()) - .extracting("body") - .isEqualTo(person2)) - .assertNext((message) -> - assertThat(message.getPayload()) - .extracting("body") - .isEqualTo(person3)) - .thenCancel() - .verifyLater(); - - Flux.concat(this.mongoTemplate.insert(person1), - this.mongoTemplate.insert(person2), - this.mongoTemplate.insert(person3)) - .as(StepVerifier::create) - .expectNextCount(3) - .verifyComplete(); - - stepVerifier.verify(Duration.ofSeconds(10)); - - this.changeStreamMessageProducer.stop(); - } - - @Configuration - @EnableIntegration - static class Config { - - @Bean - ReactiveMongoOperations mongoTemplate() { - return new ReactiveMongoTemplate(mongoClient, "test"); - } - - @Bean - IntegrationFlow changeStreamFlow() { - return IntegrationFlow.from( - MongoDb.changeStreamInboundChannelAdapter(mongoTemplate()) - .domainType(Person.class) - .collection("person") - .extractBody(false) - .autoStartup(false) - .shouldTrack(true)) - .channel(MessageChannels.flux()) - .get(); - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSourceTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSourceTests.java deleted file mode 100644 index 94f96e51b0f..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSourceTests.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import java.util.List; - -import com.mongodb.BasicDBObject; -import org.bson.conversions.Bson; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.test.support.TestApplicationContextAware; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * @author Amol Nayak - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Yaron Yamin - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 2.2 - * - */ -class MongoDbMessageSourceTests implements MongoDbContainerTest, TestApplicationContextAware { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - @Test - void withNullMongoDBFactory() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MongoDbMessageSource((MongoDatabaseFactory) null, mock(Expression.class))); - } - - @Test - void withNullMongoTemplate() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MongoDbMessageSource((MongoOperations) null, mock(Expression.class))); - } - - @Test - void withNullQueryExpression() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MongoDbMessageSource(mock(MongoDatabaseFactory.class), null)); - } - - @Test - void validateSuccessfulQueryWithSingleElementIfOneInListAsDbObject() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson(), "data"); - - Expression queryExpression = new LiteralExpression("{'name' : 'Oleg'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - @SuppressWarnings("unchecked") - List results = ((List) messageSource.receive().getPayload()); - assertThat(results).hasSize(1); - BasicDBObject resultObject = results.get(0); - - assertThat(resultObject).containsEntry("name", "Oleg"); - } - - @Test - void validateSuccessfulQueryWithSingleElementIfOneInList() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson(), "data"); - - Expression queryExpression = new LiteralExpression("{'name' : 'Oleg'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setEntityClass(Object.class); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - @SuppressWarnings("unchecked") - List results = ((List) messageSource.receive().getPayload()); - assertThat(results).hasSize(1); - Person person = results.get(0); - assertThat(person.getName()).isEqualTo("Oleg"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void validateSuccessfulQueryWithSingleElementIfOneInListAndSingleResult() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson(), "data"); - - Expression queryExpression = new LiteralExpression("{'name' : 'Oleg'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setEntityClass(Object.class); - messageSource.setExpectSingleResult(true); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - Person person = (Person) messageSource.receive().getPayload(); - - assertThat(person.getName()).isEqualTo("Oleg"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void validateSuccessfulSubObjectQueryWithSingleElementIfOneInList() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson(), "data"); - - Expression queryExpression = new LiteralExpression("{'address.state' : 'PA'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setEntityClass(Object.class); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - @SuppressWarnings("unchecked") - List results = ((List) messageSource.receive().getPayload()); - Person person = results.get(0); - assertThat(person.getName()).isEqualTo("Oleg"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void validateSuccessfulQueryWithMultipleElements() { - List persons = queryMultipleElements(new LiteralExpression("{'address.state' : 'PA'}")); - assertThat(persons).hasSize(3); - } - - @Test - void validateSuccessfulStringQueryExpressionWithMultipleElements() { - List persons = queryMultipleElements(new SpelExpressionParser() - .parseExpression("\"{'address.state' : 'PA'}\"")); - assertThat(persons).hasSize(3); - } - - @Test - void validateSuccessfulBasicQueryExpressionWithMultipleElements() { - List persons = queryMultipleElements(new SpelExpressionParser() - .parseExpression("new BasicQuery(\"{'address.state' : 'PA'}\").limit(2)")); - assertThat(persons).hasSize(2); - } - - @SuppressWarnings("unchecked") - private List queryMultipleElements(Expression queryExpression) { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson("Manny"), "data"); - template.save(MongoDbContainerTest.createPerson("Moe"), "data"); - template.save(MongoDbContainerTest.createPerson("Jack"), "data"); - - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - - return (List) messageSource.receive().getPayload(); - } - - @Test - void validateSuccessfulQueryWithNullReturn() { - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson("Manny"), "data"); - template.save(MongoDbContainerTest.createPerson("Moe"), "data"); - template.save(MongoDbContainerTest.createPerson("Jack"), "data"); - - Expression queryExpression = new LiteralExpression("{'address.state' : 'NJ'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - assertThat(messageSource.receive()).isNull(); - } - - @SuppressWarnings("unchecked") - @Test - void validateSuccessfulQueryWithCustomConverter() { - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - template.save(MongoDbContainerTest.createPerson("Manny"), "data"); - template.save(MongoDbContainerTest.createPerson("Moe"), "data"); - template.save(MongoDbContainerTest.createPerson("Jack"), "data"); - - Expression queryExpression = new LiteralExpression("{'address.state' : 'PA'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - MappingMongoConverter converter = new TestMongoConverter(MONGO_DATABASE_FACTORY, new MongoMappingContext()); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - converter.afterPropertiesSet(); - converter = spy(converter); - messageSource.setMongoConverter(converter); - messageSource.afterPropertiesSet(); - - List persons = (List) messageSource.receive().getPayload(); - assertThat(persons).hasSize(3); - verify(converter, times(3)).read((Class) Mockito.any(), Mockito.any(Bson.class)); - } - - @SuppressWarnings("unchecked") - @Test - void validateSuccessfulQueryWithMongoTemplateAndUpdate() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - MappingMongoConverter converter = new TestMongoConverter(MONGO_DATABASE_FACTORY, new MongoMappingContext()); - converter.afterPropertiesSet(); - converter = spy(converter); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY, converter); - Expression queryExpression = new LiteralExpression("{'address.state' : 'PA'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(template, queryExpression); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.setUpdateExpression(new LiteralExpression("{ $set: {'address.state' : 'NJ'} }")); - messageSource.afterPropertiesSet(); - - MongoTemplate writingTemplate = new MongoTemplate(MONGO_DATABASE_FACTORY, converter); - writingTemplate.save(MongoDbContainerTest.createPerson("Manny"), "data"); - writingTemplate.save(MongoDbContainerTest.createPerson("Moe"), "data"); - writingTemplate.save(MongoDbContainerTest.createPerson("Jack"), "data"); - - List persons = (List) messageSource.receive().getPayload(); - assertThat(persons).hasSize(3); - verify(converter, times(3)).read((Class) Mockito.any(), Mockito.any(Bson.class)); - - assertThat(messageSource.receive()).isNull(); - - assertThat(template.find(new BasicQuery("{'address.state' : 'NJ'}"), Object.class, "data")) - .hasSize(3); - } - - @Test - void validatePipelineInModifyOut() { - - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - - MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - - template.save(BasicDBObject.parse("{'name' : 'Manny', 'id' : 1}"), "data"); - - Expression queryExpression = new LiteralExpression("{'name' : 'Manny'}"); - MongoDbMessageSource messageSource = new MongoDbMessageSource(MONGO_DATABASE_FACTORY, queryExpression); - messageSource.setExpectSingleResult(true); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - BasicDBObject result = (BasicDBObject) messageSource.receive().getPayload(); - Object id = result.get("_id"); - result.put("company", "PepBoys"); - template.save(result, "data"); - result = (BasicDBObject) messageSource.receive().getPayload(); - assertThat(result).containsEntry("_id", id); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/ReactiveMongoDbMessageSourceTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/ReactiveMongoDbMessageSourceTests.java deleted file mode 100644 index 92633275fe0..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/ReactiveMongoDbMessageSourceTests.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.inbound; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import com.mongodb.BasicDBObject; -import org.bson.conversions.Bson; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.PollerSpec; -import org.springframework.integration.dsl.Pollers; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.mongodb.dsl.MongoDb; -import org.springframework.integration.test.support.TestApplicationContextAware; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * @author David Turanski - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.3 - */ -class ReactiveMongoDbMessageSourceTests implements MongoDbContainerTest, TestApplicationContextAware { - - static ReactiveMongoDatabaseFactory REACTIVE_MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - REACTIVE_MONGO_DATABASE_FACTORY = MongoDbContainerTest.createReactiveMongoDbFactory(); - } - - @Test - void withNullMongoDBFactory() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ReactiveMongoDbMessageSource((ReactiveMongoDatabaseFactory) null, - mock(Expression.class))); - } - - @Test - void withNullMongoTemplate() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ReactiveMongoDbMessageSource((ReactiveMongoTemplate) null, - mock(Expression.class))); - } - - @Test - void withNullQueryExpression() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ReactiveMongoDbMessageSource(mock(ReactiveMongoDatabaseFactory.class), - null)); - } - - @Test - @SuppressWarnings("unchecked") - void validateSuccessfulQueryWithSingleElementFluxOfDbObject() { - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY); - - ReactiveMongoTemplate template = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - waitFor(template.save(MongoDbContainerTest.createPerson(), "data")); - - Expression queryExpression = new LiteralExpression("{'name' : 'Oleg'}"); - ReactiveMongoDbMessageSource messageSource = new ReactiveMongoDbMessageSource(REACTIVE_MONGO_DATABASE_FACTORY, - queryExpression); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - - StepVerifier.create((Flux) messageSource.receive().getPayload()) - .assertNext(basicDBObject -> assertThat(basicDBObject).containsEntry("name", "Oleg")) - .verifyComplete(); - } - - @Test - @SuppressWarnings("unchecked") - void validateSuccessfulQueryWithSingleElementFluxOfPerson() { - - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY); - - ReactiveMongoTemplate template = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - waitFor(template.save(MongoDbContainerTest.createPerson(), "data")); - - Expression queryExpression = new LiteralExpression("{'name' : 'Oleg'}"); - ReactiveMongoDbMessageSource messageSource = new ReactiveMongoDbMessageSource(REACTIVE_MONGO_DATABASE_FACTORY, - queryExpression); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - messageSource.setEntityClass(Person.class); - - StepVerifier.create((Flux) messageSource.receive().getPayload()) - .assertNext(person -> assertThat(person.getName()).isEqualTo("Oleg")) - .verifyComplete(); - } - - @Test - void validateSuccessfulQueryWithMultipleElements() { - final List names = new ArrayList<>(Arrays.asList("Manny", "Moe", "Jack")); - StepVerifier.create(queryMultipleElements(new LiteralExpression("{'address.state' : 'PA'}"))) - .expectNextMatches(person -> { - names.remove(person.getName()); - return names.size() == 2; - }) - .expectNextMatches(person -> { - names.remove(person.getName()); - return names.size() == 1; - }) - .expectNextMatches(person -> { - names.remove(person.getName()); - return names.isEmpty(); - }) - .verifyComplete(); - } - - @Test - void validateSuccessfulQueryWithEmptyReturn() { - StepVerifier.create(queryMultipleElements(new LiteralExpression("{'address.state' : 'NJ'}"))) - .verifyComplete(); - } - - @Test - @SuppressWarnings("unchecked") - void validateSuccessfulQueryWithCustomConverter() { - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY); - MappingMongoConverter converter = new ReactiveTestMongoConverter( - REACTIVE_MONGO_DATABASE_FACTORY, - new MongoMappingContext()); - converter.afterPropertiesSet(); - converter = spy(converter); - StepVerifier.create(queryMultipleElements(new LiteralExpression("{'address.state' : 'PA'}"), - Optional.of(converter))).expectNextCount(3) - .verifyComplete(); - - verify(converter, times(3)).read((Class) Mockito.any(), Mockito.any(Bson.class)); - } - - @Test - void validateWithConfiguredPollerFlow() { - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY); - ReactiveMongoTemplate template = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - - waitFor(template.save(MongoDbContainerTest.createPerson(), "data")); - - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(TestContext.class); - FluxMessageChannel output = context.getBean(FluxMessageChannel.class); - StepVerifier.create(output) - .assertNext( - message -> assertThat(((Person) message.getPayload()).getName()).isEqualTo("Oleg")) - .expectNoEvent(Duration.ofMillis(100)) - .thenCancel() - .verify(Duration.ofSeconds(10)); - - context.close(); - } - - @Test - @SuppressWarnings("unchecked") - void validatePipelineInModifyOut() { - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY); - ReactiveMongoTemplate template = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - - waitFor(template.save(BasicDBObject.parse("{'name' : 'Manny', 'id' : 1}"), "data")); - - Expression queryExpression = new LiteralExpression("{'name' : 'Manny'}"); - ReactiveMongoDbMessageSource messageSource = new ReactiveMongoDbMessageSource(REACTIVE_MONGO_DATABASE_FACTORY, - queryExpression); - messageSource.setExpectSingleResult(true); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - BasicDBObject result = waitFor((Mono) messageSource.receive().getPayload()); - Object id = result.get("_id"); - result.put("company", "PepBoys"); - waitFor(template.save(result, "data")); - result = waitFor((Mono) messageSource.receive().getPayload()); - assertThat(result).containsEntry("_id", id); - } - - private Flux queryMultipleElements(Expression queryExpression) { - return queryMultipleElements(queryExpression, Optional.empty()); - } - - @SuppressWarnings("unchecked") - private Flux queryMultipleElements(Expression queryExpression, Optional converter) { - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY); - - ReactiveMongoTemplate template = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - waitFor(template.save(MongoDbContainerTest.createPerson("Manny"), "data")); - waitFor(template.save(MongoDbContainerTest.createPerson("Moe"), "data")); - waitFor(template.save(MongoDbContainerTest.createPerson("Jack"), "data")); - - ReactiveMongoDbMessageSource messageSource = new ReactiveMongoDbMessageSource(REACTIVE_MONGO_DATABASE_FACTORY, - queryExpression); - messageSource.setBeanFactory(TEST_INTEGRATION_CONTEXT); - messageSource.setEntityClass(Person.class); - converter.ifPresent(messageSource::setMongoConverter); - messageSource.setApplicationContext(TEST_INTEGRATION_CONTEXT); - messageSource.afterPropertiesSet(); - - return (Flux) messageSource.receive().getPayload(); - } - - private static T waitFor(Mono mono) { - return mono.block(Duration.ofSeconds(10)); - } - - @Configuration - @EnableIntegration - static class TestContext { - - @Bean - public IntegrationFlow pollingFlow() { - return IntegrationFlow - .from(MongoDb.reactiveInboundChannelAdapter( - REACTIVE_MONGO_DATABASE_FACTORY, "{'name' : 'Oleg'}") - .update(Update.update("name", "DONE")) - .entityClass(Person.class), - c -> c.poller(pollOnceAfter100ms())) - .split() - .channel(c -> c.flux("output")) - .get(); - } - - private PollerSpec pollOnceAfter100ms() { - return Pollers.fixedDelay(Duration.ofMinutes(5), Duration.ofMillis(100)); - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/metadata/MongoDbMetadataStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/metadata/MongoDbMetadataStoreTests.java deleted file mode 100644 index 8bd4bf50197..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/metadata/MongoDbMetadataStoreTests.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.metadata; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.integration.mongodb.MongoDbContainerTest; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Senthil Arumugam, Samiraj Panneer Selvam - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 4.2 - * - */ -class MongoDbMetadataStoreTests implements MongoDbContainerTest { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - private static final String DEFAULT_COLLECTION_NAME = "metadataStore"; - - private final String file1 = "/remotepath/filesTodownload/file-1.txt"; - - private final String file1Id = "12345"; - - private MongoDbMetadataStore store = null; - - @BeforeEach - public void configure() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, DEFAULT_COLLECTION_NAME); - this.store = new MongoDbMetadataStore(MONGO_DATABASE_FACTORY); - } - - @Test - void testConfigureCustomCollection() { - final String collectionName = "testMetadataStore"; - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, collectionName); - final MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY); - store = new MongoDbMetadataStore(template, collectionName); - testBasics(); - } - - @Test - void testConfigureFactory() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, DEFAULT_COLLECTION_NAME); - store = new MongoDbMetadataStore(MONGO_DATABASE_FACTORY); - testBasics(); - } - - @Test - void testConfigureFactorCustomCollection() { - final String collectionName = "testMetadataStore"; - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY, collectionName); - store = new MongoDbMetadataStore(MONGO_DATABASE_FACTORY, collectionName); - testBasics(); - } - - private void testBasics() { - String fileID = store.get(file1); - assertThat(fileID).isNull(); - - store.put(file1, file1Id); - - fileID = store.get(file1); - assertThat(fileID).isNotNull(); - assertThat(fileID).isEqualTo(file1Id); - } - - @Test - void testGetFromStore() { - testBasics(); - } - - @Test - void testPutIfAbsent() { - String fileID = store.get(file1); - assertThat(fileID).as("Get First time, Value must not exist").isNull(); - - fileID = store.putIfAbsent(file1, file1Id); - assertThat(fileID).as("Insert First time, Value must return null").isNull(); - - fileID = store.putIfAbsent(file1, "56789"); - assertThat(fileID) - .as("Key Already Exists - Insertion Failed, ol value must be returned").isNotNull() - .as("The Old Value must be equal to returned").isEqualTo(file1Id); - - assertThat(store.get(file1)).as("The Old Value must return").isEqualTo(file1Id); - - } - - @Test - void testRemove() { - String fileID = store.remove(file1); - assertThat(fileID).isNull(); - - fileID = store.putIfAbsent(file1, file1Id); - assertThat(fileID).isNull(); - - fileID = store.remove(file1); - assertThat(fileID) - .isNotNull() - .isEqualTo(file1Id); - - fileID = store.get(file1); - assertThat(fileID).isNull(); - } - - @Test - void testReplace() { - boolean removedValue = store.replace(file1, file1Id, "4567"); - assertThat(removedValue).isFalse(); - String fileID = store.get(file1); - assertThat(fileID).isNull(); - - fileID = store.putIfAbsent(file1, file1Id); - assertThat(fileID).isNull(); - - removedValue = store.replace(file1, file1Id, "4567"); - assertThat(removedValue).isTrue(); - - fileID = store.get(file1); - assertThat(fileID) - .isNotNull() - .isEqualTo("4567"); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml deleted file mode 100644 index bd10fabecf5..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java deleted file mode 100644 index a06ba672003..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -import org.bson.Document; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.BulkOperations; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Xavier Padro - * @author Gary Rssell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -@SpringJUnitConfig -@DirtiesContext -class MongoDbOutboundGatewayTests implements MongoDbContainerTest, TestApplicationContextAware { - - private static final String COLLECTION_NAME = "data"; - - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - - @Autowired - private BeanFactory beanFactory; - - @Autowired - private MongoOperations mongoTemplate; - - @Autowired - private MongoConverter mongoConverter; - - @Autowired - private MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeEach - public void setUp() { - BulkOperations bulkOperations = this.mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, COLLECTION_NAME); - bulkOperations.insert(Arrays.asList( - MongoDbContainerTest.createPerson("Artem"), - MongoDbContainerTest.createPerson("Gary"), - MongoDbContainerTest.createPerson("Oleg"), - MongoDbContainerTest.createPerson("Xavi"))); - bulkOperations.execute(); - } - - @AfterEach - public void cleanUp() { - mongoTemplate.dropCollection(COLLECTION_NAME); - } - - @Test - void testNoFactorySpecified() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MongoDbOutboundGateway((MongoDatabaseFactory) null)) - .withStackTraceContaining("MongoDbFactory translator must not be null"); - } - - @Test - void testNoTemplateSpecified() { - try { - new MongoDbOutboundGateway((MongoTemplate) null); - fail("Expected the test case to throw an IllegalArgumentException"); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo("mongoTemplate must not be null."); - } - } - - @Test - void testNoQuerySpecified() { - Message message = MessageBuilder.withPayload("test").build(); - MongoDbOutboundGateway gateway = createGateway(); - - try { - gateway.afterPropertiesSet(); - gateway.handleRequestMessage(message); - fail("Expected the test case to throw an IllegalArgumentException"); - } - catch (IllegalStateException e) { - assertThat(e.getMessage()).isEqualTo("no query or collectionCallback is specified"); - } - } - - @Test - void testListOfResultsWithQueryExpressionAndLimit() { - Message message = MessageBuilder.withPayload("").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setQueryExpression( - PARSER.parseExpression("new BasicQuery('{''address.state'' : ''PA''}').limit(2)")); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - List persons = getPersonsFromResult(result); - assertThat(persons).hasSize(2); - } - - @Test - void testListOfResultsWithQueryFunction() { - Message message = MessageBuilder.withPayload("Xavi").build(); - MongoDbOutboundGateway gateway = createGateway(); - Function, Query> queryFunction = - msg -> new BasicQuery("{'name' : '" + msg.getPayload() + "'}"); - FunctionExpression> functionExpression = new FunctionExpression<>(queryFunction); - gateway.setQueryExpression(functionExpression); - gateway.setExpectSingleResult(true); - gateway.setEntityClass(Person.class); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - Person person = (Person) result; - - assertThat(person.getName()).isEqualTo("Xavi"); - } - - @Test - void testListOfResultsWithQueryExpressionNotInitialized() { - MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(MONGO_DATABASE_FACTORY); - gateway.setBeanFactory(beanFactory); - gateway.setMongoConverter(mongoConverter); - try { - gateway.afterPropertiesSet(); - fail("Expected the test case to throw an IllegalStateException"); - } - catch (IllegalStateException e) { - assertThat(e.getMessage()).isEqualTo("no query or collectionCallback is specified"); - } - } - - @Test - void testListOfResultsWithQueryExpression() { - Message message = MessageBuilder.withPayload("{}").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setEntityClass(Person.class); - gateway.setQueryExpression(PARSER.parseExpression("payload")); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - List persons = getPersonsFromResult(result); - assertThat(persons).hasSize(4); - } - - @Test - void testListOfResultsWithQueryExpressionReturningOneResult() { - Message message = MessageBuilder.withPayload("{name : 'Xavi'}").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setEntityClass(Person.class); - gateway.setQueryExpression(PARSER.parseExpression("payload")); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - List persons = getPersonsFromResult(result); - assertThat(persons).hasSize(1); - assertThat(persons.get(0).getName()).isEqualTo("Xavi"); - } - - @Test - void testSingleResultWithQueryExpressionAsString() { - Message message = MessageBuilder.withPayload("{name : 'Artem'}").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setQueryExpression(PARSER.parseExpression("payload")); - gateway.setExpectSingleResult(true); - gateway.setEntityClass(Person.class); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - Person person = (Person) result; - assertThat(person.getName()).isEqualTo("Artem"); - } - - @Test - void testSingleResultWithQueryExpressionAsQuery() { - Message message = MessageBuilder.withPayload("").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setQueryExpression(PARSER.parseExpression("new BasicQuery('{''name'' : ''Gary''}')")); - gateway.setExpectSingleResult(true); - gateway.setEntityClass(Person.class); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - Person person = (Person) result; - assertThat(person.getName()).isEqualTo("Gary"); - } - - @Test - void testSingleResultWithQueryExpressionAndNoEntityClass() { - Message message = MessageBuilder.withPayload("").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setQueryExpression(new LiteralExpression("{name : 'Xavi'}")); - gateway.setExpectSingleResult(true); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - Document person = (Document) result; - assertThat(person).containsEntry("name", "Xavi"); - } - - @Test - void testWithNullCollectionNameExpression() { - MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(MONGO_DATABASE_FACTORY); - gateway.setBeanFactory(beanFactory); - gateway.setQueryExpression(new LiteralExpression("{name : 'Xavi'}")); - gateway.setExpectSingleResult(true); - - try { - gateway.afterPropertiesSet(); - fail("Expected the test case to throw an IllegalArgumentException"); - } - catch (IllegalStateException e) { - assertThat(e.getMessage()).isEqualTo("no collection name specified"); - } - } - - @Test - void testWithCollectionNameExpressionSpecified() { - Message message = MessageBuilder.withPayload("").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setQueryExpression(new LiteralExpression("{name : 'Xavi'}")); - gateway.setExpectSingleResult(true); - gateway.setCollectionNameExpression(new LiteralExpression("anotherCollection")); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - Object result = gateway.handleRequestMessage(message); - - assertThat(result).isNull(); - LiteralExpression collectionNameExpression = - (LiteralExpression) TestUtils.getPropertyValue(gateway, "collectionNameExpression"); - assertThat(collectionNameExpression).isNotNull(); - assertThat(collectionNameExpression.getValue()).isEqualTo("anotherCollection"); - } - - @Test - void testWithCollectionCallbackCount() { - Message message = MessageBuilder.withPayload("").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setEntityClass(Person.class); - gateway.setCollectionNameExpression(new LiteralExpression("data")); - - gateway.setMessageCollectionCallback((collection, requestMessage) -> collection.countDocuments()); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - long result = (long) gateway.handleRequestMessage(message); - - assertThat(result).isEqualTo(4); - } - - @Test - void testWithCollectionCallbackFindOne() { - Message message = MessageBuilder.withPayload("Mike").build(); - MongoDbOutboundGateway gateway = createGateway(); - gateway.setEntityClass(Person.class); - gateway.setCollectionNameExpression(new LiteralExpression("data")); - gateway.setRequiresReply(false); - - gateway.setMessageCollectionCallback((collection, requestMessage) -> { - collection.insertOne(new Document("name", requestMessage.getPayload())); - return null; - }); - gateway.setBeanFactory(TEST_INTEGRATION_CONTEXT); - gateway.afterPropertiesSet(); - - gateway.handleRequestMessage(message); - - List persons = this.mongoTemplate.find(new Query(), Person.class, COLLECTION_NAME); - assertThat(persons).hasSize(5); - assertThat(persons.stream().anyMatch(p -> p.getName().equals("Mike"))).isTrue(); - } - - @SuppressWarnings("unchecked") - private List getPersonsFromResult(Object result) { - return (List) result; - } - - private MongoDbOutboundGateway createGateway() { - MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(MONGO_DATABASE_FACTORY); - gateway.setBeanFactory(beanFactory); - gateway.setCollectionNameExpression(new LiteralExpression("data")); - - return gateway; - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml deleted file mode 100644 index ac3fc8917d7..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java deleted file mode 100644 index de6f22a4395..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Xavier Padro - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.0 - */ -@SpringJUnitConfig -@DirtiesContext -class MongoDbOutboundGatewayXmlTests implements MongoDbContainerTest { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - private static final String COLLECTION_NAME = "data"; - - @Autowired - private ApplicationContext context; - - @BeforeEach - public void setUp() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - MongoTemplate mongoTemplate = new MongoTemplate(MONGO_DATABASE_FACTORY); - - mongoTemplate.save(MongoDbContainerTest.createPerson("Artem"), COLLECTION_NAME); - mongoTemplate.save(MongoDbContainerTest.createPerson("Gary"), COLLECTION_NAME); - mongoTemplate.save(MongoDbContainerTest.createPerson("Oleg"), COLLECTION_NAME); - mongoTemplate.save(MongoDbContainerTest.createPerson("Xavi"), COLLECTION_NAME); - } - - @AfterEach - public void cleanUp() { - MongoDbContainerTest.prepareMongoData(MONGO_DATABASE_FACTORY); - MongoTemplate mongoTemplate = new MongoTemplate(MONGO_DATABASE_FACTORY); - - mongoTemplate.dropCollection(COLLECTION_NAME); - } - - @Test - void testSingleQuery() { - EventDrivenConsumer consumer = context.getBean("gatewaySingleQuery", EventDrivenConsumer.class); - PollableChannel outChannel = context.getBean("out", PollableChannel.class); - - Message message = MessageBuilder.withPayload("").build(); - consumer.getHandler().handleMessage(message); - - Message result = outChannel.receive(10000); - Person person = getPerson(result); - assertThat(person.getName()).isEqualTo("Xavi"); - } - - @Test - void testSingleQueryWithTemplate() { - EventDrivenConsumer consumer = context.getBean("gatewayWithTemplate", EventDrivenConsumer.class); - PollableChannel outChannel = context.getBean("out", PollableChannel.class); - - Message message = MessageBuilder.withPayload("").build(); - consumer.getHandler().handleMessage(message); - - Message result = outChannel.receive(10000); - Person person = getPerson(result); - assertThat(person.getName()).isEqualTo("Xavi"); - } - - @Test - void testSingleQueryExpression() { - EventDrivenConsumer consumer = context.getBean("gatewaySingleQueryExpression", EventDrivenConsumer.class); - PollableChannel outChannel = context.getBean("out", PollableChannel.class); - - Message message = MessageBuilder - .withPayload("") - .setHeader("query", "{'name' : 'Gary'}") - .setHeader("collectionName", "data") - .build(); - - consumer.getHandler().handleMessage(message); - - Message result = outChannel.receive(10000); - Person person = getPerson(result); - assertThat(person.getName()).isEqualTo("Gary"); - } - - @Test - void testQueryExpression() { - EventDrivenConsumer consumer = context.getBean("gatewayQueryExpression", EventDrivenConsumer.class); - PollableChannel outChannel = context.getBean("out", PollableChannel.class); - - Message message = MessageBuilder - .withPayload("") - .setHeader("query", "{}") - .setHeader("collectionName", "data") - .build(); - - consumer.getHandler().handleMessage(message); - - Message result = outChannel.receive(10000); - List persons = getPersons(result); - assertThat(persons).hasSize(4); - } - - @Test - void testQueryExpressionWithLimit() { - EventDrivenConsumer consumer = context.getBean("gatewayQueryExpressionLimit", EventDrivenConsumer.class); - PollableChannel outChannel = context.getBean("out", PollableChannel.class); - - Message message = MessageBuilder - .withPayload("") - .setHeader("collectionName", "data") - .build(); - - consumer.getHandler().handleMessage(message); - - Message result = outChannel.receive(10000); - List persons = getPersons(result); - assertThat(persons).hasSize(2); - } - - @Test - void testCollectionCallback() { - EventDrivenConsumer consumer = context.getBean("gatewayCollectionCallback", EventDrivenConsumer.class); - PollableChannel outChannel = context.getBean("out", PollableChannel.class); - - Message message = MessageBuilder - .withPayload("") - .setHeader("collectionName", "data") - .build(); - - consumer.getHandler().handleMessage(message); - - Message result = outChannel.receive(10000); - long personsCount = (Long) result.getPayload(); - assertThat(personsCount).isEqualTo(4); - } - - private Person getPerson(Message message) { - return (Person) message.getPayload(); - } - - @SuppressWarnings("unchecked") - private List getPersons(Message message) { - return (List) message.getPayload(); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbStoringMessageHandlerTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbStoringMessageHandlerTests.java deleted file mode 100644 index 12308049e27..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbStoringMessageHandlerTests.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import org.bson.conversions.Bson; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.messaging.Message; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * @author Amol Nayak - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 2.2 - */ -class MongoDbStoringMessageHandlerTests implements MongoDbContainerTest, TestApplicationContextAware { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - private MongoTemplate template; - - @BeforeEach - public void setUp() { - template = new MongoTemplate(MONGO_DATABASE_FACTORY); - } - - @Test - void withNullMongoDBFactory() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MongoDbStoringMessageHandler((MongoDatabaseFactory) null)); - } - - @Test - void withNullMongoTemplate() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MongoDbStoringMessageHandler((MongoOperations) null)); - } - - @Test - void validateMessageHandlingWithDefaultCollection() { - - MongoDbStoringMessageHandler handler = new MongoDbStoringMessageHandler(MONGO_DATABASE_FACTORY); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - handler.handleMessage(message); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = template.findOne(query, Person.class, "data"); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void validateMessageHandlingWithNamedCollection() { - - MongoDbStoringMessageHandler handler = new MongoDbStoringMessageHandler(MONGO_DATABASE_FACTORY); - handler.setCollectionNameExpression(new LiteralExpression("foo")); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - handler.handleMessage(message); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = template.findOne(query, Person.class, "foo"); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void validateMessageHandlingWithMongoConverter() { - - MongoDbStoringMessageHandler handler = new MongoDbStoringMessageHandler(MONGO_DATABASE_FACTORY); - handler.setCollectionNameExpression(new LiteralExpression("foo")); - MappingMongoConverter converter = new TestMongoConverter(MONGO_DATABASE_FACTORY, new MongoMappingContext()); - converter.afterPropertiesSet(); - converter = spy(converter); - handler.setMongoConverter(converter); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - handler.handleMessage(message); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = template.findOne(query, Person.class, "foo"); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - verify(converter, times(1)).write(Mockito.any(), Mockito.any(Bson.class)); - } - - @Test - void validateMessageHandlingWithMongoTemplate() { - MappingMongoConverter converter = new TestMongoConverter(MONGO_DATABASE_FACTORY, new MongoMappingContext()); - converter.afterPropertiesSet(); - converter = spy(converter); - MongoTemplate writingTemplate = new MongoTemplate(MONGO_DATABASE_FACTORY, converter); - - MongoDbStoringMessageHandler handler = new MongoDbStoringMessageHandler(writingTemplate); - handler.setCollectionNameExpression(new LiteralExpression("foo")); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - handler.handleMessage(message); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = template.findOne(query, Person.class, "foo"); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - verify(converter, times(1)).write(Mockito.any(), Mockito.any(Bson.class)); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandlerTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandlerTests-context.xml deleted file mode 100644 index 796319486aa..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandlerTests-context.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandlerTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandlerTests.java deleted file mode 100644 index a843ec3c7ed..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/ReactiveMongoDbStoringMessageHandlerTests.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.outbound; - -import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Answers; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.mapping.MongoMappingContext; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; - -/** - * @author Amol Nayak - * @author Oleg Zhurakousky - * @author Gary Russell - * @author David Turanski - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.3 - */ -@SpringJUnitConfig -@DirtiesContext -class ReactiveMongoDbStoringMessageHandlerTests implements MongoDbContainerTest, TestApplicationContextAware { - - public static ReactiveMongoDatabaseFactory REACTIVE_MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - REACTIVE_MONGO_DATABASE_FACTORY = MongoDbContainerTest.createReactiveMongoDbFactory(); - } - - private ReactiveMongoTemplate template; - - @Autowired - private MessageChannel input; - - @BeforeEach - public void setUp() { - MongoDbContainerTest.prepareReactiveMongoData(REACTIVE_MONGO_DATABASE_FACTORY, "foo"); - this.template = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY); - } - - @Test - void withNullMongoDBFactory() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ReactiveMongoDbStoringMessageHandler((ReactiveMongoDatabaseFactory) null)); - } - - @Test - void withNullMongoTemplate() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ReactiveMongoDbStoringMessageHandler((ReactiveMongoOperations) null)); - } - - @Test - void validateMessageHandlingWithDefaultCollection() { - ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(REACTIVE_MONGO_DATABASE_FACTORY); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS)); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - waitFor(handler.handleMessage(message)); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = waitFor(this.template.findOne(query, Person.class, "data")); - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - - } - - @Test - void validateMessageHandlingWithNamedCollection() { - ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(REACTIVE_MONGO_DATABASE_FACTORY); - handler.setCollectionNameExpression(new LiteralExpression("foo")); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS)); - handler.afterPropertiesSet(); - - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - waitFor(handler.handleMessage(message)); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = waitFor(this.template.findOne(query, Person.class, "foo")); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - - } - - @Test - void errorOnMessageHandlingWithNullValuedExpression() { - - ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(REACTIVE_MONGO_DATABASE_FACTORY); - handler.setCollectionNameExpression(new LiteralExpression(null)); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS)); - handler.afterPropertiesSet(); - - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - AtomicBoolean errorOccurred = new AtomicBoolean(); - handler.handleMessage(message) - .doOnError(e -> errorOccurred.set(true)) - .subscribe(aVoid -> assertThat(errorOccurred.get()).isTrue()); - } - - @Test - void validateMessageHandlingWithMongoConverter() { - ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(REACTIVE_MONGO_DATABASE_FACTORY); - handler.setCollectionNameExpression(new LiteralExpression("foo")); - MappingMongoConverter converter = - new ReactiveTestMongoConverter(REACTIVE_MONGO_DATABASE_FACTORY, new MongoMappingContext()); - converter.afterPropertiesSet(); - converter = spy(converter); - handler.setMongoConverter(converter); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS)); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - waitFor(handler.handleMessage(message)); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = waitFor(this.template.findOne(query, Person.class, "foo")); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void validateMessageHandlingWithMongoTemplate() { - MappingMongoConverter converter = - new ReactiveTestMongoConverter(REACTIVE_MONGO_DATABASE_FACTORY, new MongoMappingContext()); - converter.afterPropertiesSet(); - converter = spy(converter); - ReactiveMongoTemplate writingTemplate = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY, converter); - ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(writingTemplate); - handler.setCollectionNameExpression(new LiteralExpression("foo")); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS)); - handler.afterPropertiesSet(); - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - waitFor(handler.handleMessage(message)); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - Person person = waitFor(this.template.findOne(query, Person.class, "foo")); - - assertThat(person.getName()).isEqualTo("Bob"); - assertThat(person.getAddress().getState()).isEqualTo("PA"); - } - - @Test - void testReactiveMongoMessageHandlerFromApplicationContext() { - Message message = MessageBuilder.withPayload(MongoDbContainerTest.createPerson("Bob")).build(); - this.input.send(message); - - Query query = new BasicQuery("{'name' : 'Bob'}"); - - await().untilAsserted(() -> - assertThat(waitFor(this.template.findOne(query, Person.class, "data"))) - .isNotNull() - .extracting("name", "address.state").contains("Bob", "PA")); - } - - private static T waitFor(Mono mono) { - return mono.block(Duration.ofSeconds(10)); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/AbstractMongoDbMessageGroupStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/AbstractMongoDbMessageGroupStoreTests.java deleted file mode 100644 index 751242ee4fd..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/AbstractMongoDbMessageGroupStoreTests.java +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; -import java.util.UUID; -import java.util.function.BiFunction; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.integration.aggregator.ReleaseStrategy; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.store.AbstractBatchingMessageGroupStore; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.store.MessageStore; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Amol Nayak - * @author Artem Bilan - * @author Artem Vozhdayenko - */ -public abstract class AbstractMongoDbMessageGroupStoreTests implements MongoDbContainerTest { - - public static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - public static final BiFunction, String, String> CONDITION_SUPPLIER = (m, c) -> "10"; - - public static final ReleaseStrategy RELEASE_STRATEGY = - group -> group.size() == Integer.parseInt(group.getCondition()); - - protected final GenericApplicationContext testApplicationContext = TestUtils.createTestApplicationContext(); - - @BeforeEach - public void setup() { - this.testApplicationContext.refresh(); - } - - @AfterEach - public void tearDown() { - this.testApplicationContext.close(); - MongoDbContainerTest.cleanupCollections(MONGO_DATABASE_FACTORY); - } - - @Test - void testNonExistingEmptyMessageGroup() { - MessageGroupStore store = getMessageGroupStore(); - store.addMessagesToGroup(1, new GenericMessage("foo")); - MessageGroup messageGroup = store.getMessageGroup(1); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.getClass().getName()).contains("PersistentMessageGroup"); - assertThat(messageGroup.size()).isEqualTo(1); - } - - @Test - void testMessageGroupWithAddedMessagePrimitiveGroupId() { - MessageGroupStore store = getMessageGroupStore(); - MessageStore messageStore = getMessageStore(); - MessageGroup messageGroup = store.getMessageGroup(1); - Message messageA = new GenericMessage<>("A"); - Message messageB = new GenericMessage<>("B"); - store.addMessagesToGroup(1, messageA); - messageGroup = store.addMessageToGroup(1, messageB); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(2); - Message retrievedMessage = messageStore.getMessage(messageA.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(messageA.getHeaders().getId()).isEqualTo(retrievedMessage.getHeaders().getId()); - // ensure that 'message_group' header that is only used internally is not propagated - assertThat(retrievedMessage.getHeaders().get("message_group")).isNull(); - assertThat(store.getMessageGroupCount()).isEqualTo(1); - } - - @Test - void testMessageGroupWithAddedMessageUUIDGroupIdAndUUIDHeader() { - MessageGroupStore store = getMessageGroupStore(); - MessageStore messageStore = getMessageStore(); - Object id = UUID.randomUUID(); - MessageGroup messageGroup = store.getMessageGroup(id); - UUID uuidA = UUID.randomUUID(); - Message messageA = MessageBuilder.withPayload("A").setHeader("foo", uuidA).build(); - UUID uuidB = UUID.randomUUID(); - Message messageB = MessageBuilder.withPayload("B").setHeader("foo", uuidB).build(); - store.addMessagesToGroup(id, messageA); - messageGroup = store.addMessageToGroup(id, messageB); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(2); - Message retrievedMessage = messageStore.getMessage(messageA.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(messageA.getHeaders().getId()).isEqualTo(retrievedMessage.getHeaders().getId()); - // ensure that 'message_group' header that is only used internally is not propagated - assertThat(retrievedMessage.getHeaders().get("message_group")).isNull(); - Object fooHeader = retrievedMessage.getHeaders().get("foo"); - assertThat(fooHeader) - .isInstanceOf(UUID.class) - .isEqualTo(uuidA); - } - - @Test - void testCountMessagesInGroup() { - MessageGroupStore store = getMessageGroupStore(); - Message messageA = new GenericMessage<>("A"); - Message messageB = new GenericMessage<>("B"); - store.addMessagesToGroup(1, messageA, messageB); - assertThat(store.messageGroupSize(1)).isEqualTo(2); - } - - @Test - void testPollMessages() throws InterruptedException { - MessageGroupStore store = getMessageGroupStore(); - Message messageA = new GenericMessage<>("A"); - Message messageB = new GenericMessage<>("B"); - store.addMessagesToGroup(1, messageA); - Thread.sleep(10); - store.addMessagesToGroup(1, messageB); - assertThat(store.messageGroupSize(1)).isEqualTo(2); - Message out = store.pollMessageFromGroup(1); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo("A"); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - out = store.pollMessageFromGroup(1); - assertThat(out.getPayload()).isEqualTo("B"); - assertThat(store.messageGroupSize(1)).isZero(); - } - - @Test - void testSameMessageMultipleGroupsPoll() { - MessageGroupStore store = getMessageGroupStore(); - Message messageA = new GenericMessage<>("A"); - store.addMessagesToGroup(1, messageA); - store.addMessagesToGroup(2, messageA); - store.addMessagesToGroup(3, messageA); - store.addMessagesToGroup(4, messageA); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isEqualTo(1); - assertThat(store.messageGroupSize(3)).isEqualTo(1); - assertThat(store.messageGroupSize(4)).isEqualTo(1); - store.pollMessageFromGroup(3); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isEqualTo(1); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isEqualTo(1); - store.pollMessageFromGroup(4); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isEqualTo(1); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isZero(); - store.pollMessageFromGroup(2); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isZero(); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isZero(); - store.pollMessageFromGroup(1); - assertThat(store.messageGroupSize(1)).isZero(); - assertThat(store.messageGroupSize(2)).isZero(); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isZero(); - } - - @Test - void testSameMessageMultipleGroupsRemove() { - MessageGroupStore store = getMessageGroupStore(); - Message messageA = new GenericMessage<>("A"); - store.addMessagesToGroup(1, messageA); - store.addMessagesToGroup(2, messageA); - store.addMessagesToGroup(3, messageA); - store.addMessagesToGroup(4, messageA); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isEqualTo(1); - assertThat(store.messageGroupSize(3)).isEqualTo(1); - assertThat(store.messageGroupSize(4)).isEqualTo(1); - store.removeMessagesFromGroup(3, messageA); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isEqualTo(1); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isEqualTo(1); - store.removeMessagesFromGroup(4, messageA); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isEqualTo(1); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isZero(); - store.removeMessagesFromGroup(2, messageA); - assertThat(store.messageGroupSize(1)).isEqualTo(1); - assertThat(store.messageGroupSize(2)).isZero(); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isZero(); - store.removeMessagesFromGroup(1, messageA); - assertThat(store.messageGroupSize(1)).isZero(); - assertThat(store.messageGroupSize(2)).isZero(); - assertThat(store.messageGroupSize(3)).isZero(); - assertThat(store.messageGroupSize(4)).isZero(); - } - - @Test - void testMessageGroupUpdatedDateChangesWithEachAddedMessage() throws InterruptedException { - MessageGroupStore store = getMessageGroupStore(); - - MessageGroup messageGroup = store.getMessageGroup(1); - Message message = new GenericMessage<>("Hello"); - messageGroup = store.addMessageToGroup(1, message); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(1); - long createdTimestamp = messageGroup.getTimestamp(); - long updatedTimestamp = messageGroup.getLastModified(); - assertThat(updatedTimestamp).isEqualTo(createdTimestamp); - Thread.sleep(10); - message = new GenericMessage<>("Hello again"); - messageGroup = store.addMessageToGroup(1, message); - createdTimestamp = messageGroup.getTimestamp(); - updatedTimestamp = messageGroup.getLastModified(); - assertThat(updatedTimestamp > createdTimestamp).isTrue(); - assertThat(messageGroup.size()).isEqualTo(2); - - // make sure the store is properly rebuild from MongoDB - store = getMessageGroupStore(); - - messageGroup = store.getMessageGroup(1); - assertThat(messageGroup.size()).isEqualTo(2); - } - - @Test - void testMessageGroupMarkingMessage() { - MessageGroupStore store = getMessageGroupStore(); - - MessageGroup messageGroup = store.getMessageGroup(1); - Message messageA = new GenericMessage<>("A"); - Message messageB = new GenericMessage<>("B"); - store.addMessagesToGroup(1, messageA); - messageGroup = store.addMessageToGroup(1, messageB); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(2); - - store.removeMessagesFromGroup(1, messageA); - messageGroup = store.getMessageGroup(1); - assertThat(messageGroup.size()).isEqualTo(1); - - // validate that the updates were propagated to Mongo as well - store = this.getMessageGroupStore(); - - messageGroup = store.getMessageGroup(1); - assertThat(messageGroup.size()).isEqualTo(1); - } - - @Test - void testRemoveMessageGroup() { - MessageGroupStore store = getMessageGroupStore(); - MessageStore messageStore = getMessageStore(); - - MessageGroup messageGroup = store.getMessageGroup(1); - Message message = new GenericMessage<>("Hello"); - UUID id = message.getHeaders().getId(); - messageGroup = store.addMessageToGroup(1, message); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(1); - message = messageStore.getMessage(id); - assertThat(message).isNotNull(); - - store.removeMessageGroup(1); - MessageGroup messageGroupA = store.getMessageGroup(1); - assertThat(messageGroupA.size()).isZero(); - assertThat(messageGroupA.equals(messageGroup)).isFalse(); - - } - - @Test - void testCompleteMessageGroup() { - MessageGroupStore store = getMessageGroupStore(); - - MessageGroup messageGroup = store.getMessageGroup(1); - assertThat(messageGroup).isNotNull(); - Message message = new GenericMessage<>("Hello"); - store.addMessagesToGroup(messageGroup.getGroupId(), message); - store.completeGroup(messageGroup.getGroupId()); - messageGroup = store.getMessageGroup(1); - assertThat(messageGroup.isComplete()).isTrue(); - } - - @Test - void testLastReleasedSequenceNumber() { - MessageGroupStore store = getMessageGroupStore(); - - MessageGroup messageGroup = store.getMessageGroup(1); - assertThat(messageGroup).isNotNull(); - Message message = new GenericMessage<>("Hello"); - store.addMessagesToGroup(messageGroup.getGroupId(), message); - store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5); - messageGroup = store.getMessageGroup(1); - assertThat(messageGroup.getLastReleasedMessageSequenceNumber()).isEqualTo(5); - } - - @Test - void testRemoveMessageFromTheGroup() { - MessageGroupStore store = getMessageGroupStore(); - - MessageGroup messageGroup = store.getMessageGroup(1); - Message message = new GenericMessage<>("2"); - store.addMessagesToGroup(1, new GenericMessage<>("1"), message); - messageGroup = store.addMessageToGroup(1, new GenericMessage<>("3")); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(3); - - store.removeMessagesFromGroup(1, message); - messageGroup = store.getMessageGroup(1); - assertThat(messageGroup.size()).isEqualTo(2); - } - - @Test - void testMultipleMessageStores() { - MessageGroupStore store1 = getMessageGroupStore(); - MessageGroupStore store2 = getMessageGroupStore(); - - Message message = new GenericMessage<>("1"); - store1.addMessagesToGroup(1, message, new GenericMessage<>("2"), new GenericMessage<>("3")); - - MessageGroupStore store3 = getMessageGroupStore(); - - MessageGroup messageGroup = store3.getMessageGroup(1); - - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup.size()).isEqualTo(3); - - store3.removeMessagesFromGroup(1, message); - - messageGroup = store2.getMessageGroup(1); - assertThat(messageGroup.size()).isEqualTo(2); - } - - @Test - void testMessageGroupIterator() { - MessageGroupStore store1 = getMessageGroupStore(); - MessageGroupStore store2 = getMessageGroupStore(); - - Message message = new GenericMessage<>("1"); - store2.addMessagesToGroup("1", message); - store1.addMessagesToGroup("2", new GenericMessage<>("2")); - store2.addMessagesToGroup(UUID.randomUUID(), new GenericMessage<>("3")); - - MessageGroupStore store3 = this.getMessageGroupStore(); - Iterator iterator = store3.iterator(); - assertThat(iterator).isNotNull(); - int counter = 0; - while (iterator.hasNext()) { - iterator.next(); - counter++; - } - assertThat(counter).isEqualTo(3); - - store2.removeMessagesFromGroup("1", message); - - iterator = store3.iterator(); - counter = 0; - while (iterator.hasNext()) { - iterator.next(); - counter++; - } - assertThat(counter).isEqualTo(2); - } - - @Test - void testAddAndRemoveMessagesFromMessageGroup() { - MessageGroupStore messageStore = (MessageGroupStore) getMessageStore(); - String groupId = "X"; - messageStore.removeMessageGroup("X"); - ((AbstractBatchingMessageGroupStore) messageStore).setRemoveBatchSize(10); - List> messages = new ArrayList<>(); - for (int i = 0; i < 25; i++) { - Message message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build(); - messageStore.addMessagesToGroup(groupId, message); - messages.add(message); - } - MessageGroup group = messageStore.getMessageGroup(groupId); - assertThat(group.size()).isEqualTo(25); - messageStore.removeMessagesFromGroup(groupId, messages); - group = messageStore.getMessageGroup(groupId); - assertThat(group.size()).isZero(); - } - - // @Test - // @MongoDbAvailable - // public void testConcurrentModifications() throws Exception{ - // MongoDbContainerTest.prepareMongoFactory(MONGO_DATABASE_FACTORY); - // final MongoDbMessageStore store1 = new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - // final MongoDbMessageStore store2 = new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - // - // final Message message = new GenericMessage("1"); - // - // ExecutorService executor = null; - // - // final List failures = new ArrayList(); - // - // for (int i = 0; i < 100; i++) { - // executor = Executors.newCachedThreadPool(); - // - // executor.execute(new Runnable() { - // public void run() { - // MessageGroup group = store1.addMessageToGroup(1, message); - // if (group.getUnmarked().size() != 1){ - // failures.add("ADD"); - // throw new AssertionFailedError("Failed on ADD"); - // } - // } - // }); - // executor.execute(new Runnable() { - // public void run() { - // MessageGroup group = store2.removeMessageFromGroup(1, message); - // if (group.getUnmarked().size() != 0){ - // failures.add("REMOVE"); - // throw new AssertionFailedError("Failed on Remove"); - // } - // } - // }); - // - // executor.shutdown(); - // executor.awaitTermination(10, TimeUnit.SECONDS); - // store2.removeMessageFromGroup(1, message); // ensures that if ADD thread executed after REMOVE, the - // store is empty for the next cycle - // } - // assertTrue(failures.size() == 0); - // } - - protected void testWithAggregatorWithShutdown(String config) { - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config, this.getClass()); - context.refresh(); - - MessageChannel input = context.getBean("inputChannel", MessageChannel.class); - QueueChannel output = context.getBean("outputChannel", QueueChannel.class); - - Message m1 = MessageBuilder.withPayload("1") - .setSequenceNumber(1) - .setSequenceSize(10) - .setCorrelationId(1) - .build(); - Message m2 = MessageBuilder.withPayload("2") - .setSequenceNumber(2) - .setSequenceSize(10) - .setCorrelationId(1) - .build(); - input.send(m1); - assertThat(output.receive(10)).isNull(); - input.send(m2); - assertThat(output.receive(10)).isNull(); - - for (int i = 3; i < 10; i++) { - input.send(MessageBuilder.withPayload("" + i) - .setSequenceNumber(i) - .setSequenceSize(10) - .setCorrelationId(1) - .build()); - } - - context.close(); - - context = new ClassPathXmlApplicationContext(config, this.getClass()); - input = context.getBean("inputChannel", MessageChannel.class); - output = context.getBean("outputChannel", QueueChannel.class); - - Message m10 = MessageBuilder.withPayload("10") - .setSequenceNumber(10) - .setSequenceSize(10) - .setCorrelationId(1) - .build(); - input.send(m10); - assertThat(output.receive(2000)).isNotNull(); - context.close(); - } - - @Test - void testWithMessageHistory() { - MessageGroupStore store = getMessageGroupStore(); - - store.getMessageGroup(1); - - Message message = new GenericMessage<>("Hello"); - DirectChannel fooChannel = new DirectChannel(); - fooChannel.setBeanName("fooChannel"); - DirectChannel barChannel = new DirectChannel(); - barChannel.setBeanName("barChannel"); - - message = MessageHistory.write(message, fooChannel); - message = MessageHistory.write(message, barChannel); - store.addMessagesToGroup(1, message); - MessageGroup group = store.getMessageGroup(1); - assertThat(group).isNotNull(); - Collection> messages = group.getMessages(); - assertThat(!messages.isEmpty()).isTrue(); - message = messages.iterator().next(); - - MessageHistory messageHistory = MessageHistory.read(message); - assertThat(messageHistory).isNotNull(); - assertThat(messageHistory).hasSize(2); - Properties fooChannelHistory = messageHistory.get(0); - assertThat(fooChannelHistory) - .containsEntry("name", "fooChannel") - .containsEntry("type", "channel"); - } - - @Test - public void removeMessageDoesntRemoveSameMessageInTheGroup() { - GenericMessage testMessage = new GenericMessage<>("test data"); - - MessageGroupStore store = getMessageGroupStore(); - - store.addMessageToGroup("1", testMessage); - - MessageStore messageStore = (MessageStore) store; - - messageStore.removeMessage(testMessage.getHeaders().getId()); - - assertThat(messageStore.getMessageCount()).isEqualTo(0); - assertThat(store.getMessageCountForAllMessageGroups()).isEqualTo(1); - assertThat(store.messageGroupSize("1")).isEqualTo(1); - - store.removeMessageGroup("1"); - - assertThat(store.getMessageCountForAllMessageGroups()).isEqualTo(0); - assertThat(store.messageGroupSize("1")).isEqualTo(0); - } - - protected abstract MessageGroupStore getMessageGroupStore(); - - protected abstract MessageStore getMessageStore(); - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/AbstractMongoDbMessageStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/AbstractMongoDbMessageStoreTests.java deleted file mode 100644 index d6559c77c23..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/AbstractMongoDbMessageStoreTests.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.io.Serializable; -import java.util.Properties; -import java.util.UUID; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.data.annotation.PersistenceCreator; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.message.AdviceMessage; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.store.MessageStore; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.MutableMessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Amol Nayak - * @author Artem Vozhdayenko - * - */ -public abstract class AbstractMongoDbMessageStoreTests implements MongoDbContainerTest { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - protected final GenericApplicationContext testApplicationContext = TestUtils.createTestApplicationContext(); - - @BeforeEach - public void setup() { - this.testApplicationContext.refresh(); - } - - @AfterEach - public void tearDown() { - this.testApplicationContext.close(); - MongoDbContainerTest.cleanupCollections(MONGO_DATABASE_FACTORY); - } - - @Test - void testAddGetWithStringPayload() { - MessageStore store = getMessageStore(); - Message messageToStore = MessageBuilder.withPayload("Hello").build(); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isEqualTo(messageToStore.getPayload()); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testAddThenRemoveWithStringPayload() { - MessageStore store = getMessageStore(); - Message messageToStore = MessageBuilder.withPayload("Hello").build(); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - store.removeMessage(retrievedMessage.getHeaders().getId()); - retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNull(); - } - - @Test - void testAddGetWithObjectDefaultConstructorPayload() { - MessageStore store = getMessageStore(); - Person p = new Person(); - p.setFname("John"); - p.setLname("Doe"); - Message messageToStore = MessageBuilder.withPayload(p).build(); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isEqualTo(messageToStore.getPayload()); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testWithMessageHistory() { - MessageStore store = getMessageStore(); - Foo foo = new Foo(); - foo.setName("foo"); - Message message = MessageBuilder.withPayload(foo). - setHeader("foo", foo). - setHeader("bar", new Bar("bar")). - setHeader("baz", new Baz()). - setHeader("abc", new Abc()). - setHeader("xyz", new Xyz()). - build(); - DirectChannel fooChannel = new DirectChannel(); - fooChannel.setBeanName("fooChannel"); - DirectChannel barChannel = new DirectChannel(); - barChannel.setBeanName("barChannel"); - - message = MessageHistory.write(message, fooChannel); - message = MessageHistory.write(message, barChannel); - store.addMessage(message); - message = store.getMessage(message.getHeaders().getId()); - assertThat(message).isNotNull(); - assertThat(message.getHeaders().get("foo")).isInstanceOf(Foo.class); - assertThat(message.getHeaders().get("bar")).isInstanceOf(Bar.class); - assertThat(message.getHeaders().get("baz")).isInstanceOf(Baz.class); - assertThat(message.getHeaders().get("abc")).isInstanceOf(Abc.class); - assertThat(message.getHeaders().get("xyz")).isInstanceOf(Xyz.class); - MessageHistory messageHistory = MessageHistory.read(message); - assertThat(messageHistory).isNotNull(); - assertThat(messageHistory).hasSize(2); - Properties fooChannelHistory = messageHistory.get(0); - assertThat(fooChannelHistory) - .containsEntry("name", "fooChannel") - .containsEntry("type", "channel"); - } - - @Test - void testInt3153SequenceDetails() { - MessageStore store = getMessageStore(); - Message messageToStore = MessageBuilder.withPayload("test") - .pushSequenceDetails(UUID.randomUUID(), 1, 1) - .pushSequenceDetails(UUID.randomUUID(), 1, 1) - .build(); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isEqualTo(messageToStore.getPayload()); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testInt3076MessageAsPayload() { - MessageStore store = getMessageStore(); - Person p = new Person(); - p.setFname("John"); - p.setLname("Doe"); - Message messageToStore = new GenericMessage>(MessageBuilder.withPayload(p).build()); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isInstanceOf(GenericMessage.class); - assertThat(retrievedMessage.getPayload()).isEqualTo(messageToStore.getPayload()); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(p).isEqualTo(((Message) messageToStore.getPayload()).getPayload()); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testInt3076AdviceMessage() { - MessageStore store = getMessageStore(); - Person p = new Person(); - p.setFname("John"); - p.setLname("Doe"); - Message inputMessage = MessageBuilder.withPayload(p).build(); - Message messageToStore = new AdviceMessage<>("foo", inputMessage); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage) - .isNotNull() - .isInstanceOf(AdviceMessage.class); - assertThat(retrievedMessage.getPayload()).isEqualTo(messageToStore.getPayload()); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(((AdviceMessage) retrievedMessage).getInputMessage()).isEqualTo(inputMessage); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testAdviceMessageAsPayload() { - MessageStore store = getMessageStore(); - Person p = new Person(); - p.setFname("John"); - p.setLname("Doe"); - Message inputMessage = MessageBuilder.withPayload(p).build(); - Message messageToStore = new GenericMessage>(new AdviceMessage<>("foo", inputMessage)); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isInstanceOf(AdviceMessage.class); - AdviceMessage adviceMessage = (AdviceMessage) retrievedMessage.getPayload(); - assertThat(adviceMessage.getPayload()).isEqualTo("foo"); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(adviceMessage.getInputMessage()).isEqualTo(inputMessage); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testMutableMessageAsPayload() { - MessageStore store = getMessageStore(); - Person p = new Person(); - p.setFname("John"); - p.setLname("Doe"); - Message messageToStore = new GenericMessage>(MutableMessageBuilder.withPayload(p).build()); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isInstanceOf(MutableMessage.class); - assertThat(retrievedMessage.getPayload()).isEqualTo(messageToStore.getPayload()); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - assertThat(p).isEqualTo(((Message) messageToStore.getPayload()).getPayload()); - assertThat(retrievedMessage).isEqualTo(messageToStore); - } - - @Test - void testInt3076ErrorMessage() { - MessageStore store = getMessageStore(); - Person p = new Person(); - p.setFname("John"); - p.setLname("Doe"); - Message failedMessage = MessageBuilder.withPayload(p).build(); - MessagingException messagingException; - try { - throw new RuntimeException("intentional"); - } - catch (Exception e) { - messagingException = new MessagingException(failedMessage, "intentional MessagingException", e); - } - Message messageToStore = new ErrorMessage(messagingException); - store.addMessage(messageToStore); - Message retrievedMessage = store.getMessage(messageToStore.getHeaders().getId()); - assertThat(retrievedMessage) - .isNotNull() - .isInstanceOf(ErrorMessage.class); - assertThat(retrievedMessage.getPayload()).isInstanceOf(MessagingException.class); - assertThat(((MessagingException) retrievedMessage.getPayload()).getMessage()) - .contains("intentional MessagingException"); - assertThat(((MessagingException) retrievedMessage.getPayload()).getFailedMessage()).isEqualTo(failedMessage); - assertThat(retrievedMessage.getHeaders()).isEqualTo(messageToStore.getHeaders()); - } - - @Test - void testAddAndUpdateAlreadySaved() { - MessageStore messageStore = getMessageStore(); - Message message = MessageBuilder.withPayload("foo").build(); - message = messageStore.addMessage(message); - Message result = messageStore.addMessage(message); - assertThat(result).isEqualTo(message); - } - - public static class Foo implements Serializable { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - public static class Bar implements Serializable { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private final String name; - - public Bar(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - } - - public static class Baz implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String name; - - Baz() { - this("baz"); - } - - @PersistenceCreator - Baz(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - } - - public static class Abc implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String name; - - Abc() { - this("abx"); - } - - @PersistenceCreator - Abc(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - } - - public static class Xyz implements Serializable { - - private static final long serialVersionUID = 1L; - - @SuppressWarnings("unused") - private final String name; - - Xyz() { - this("xyz"); - } - - @PersistenceCreator - Xyz(String name) { - this.name = name; - } - - } - - public static class Person implements Serializable { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private String fname; - - private String lname; - - public String getFname() { - return fname; - } - - public void setFname(String fname) { - this.fname = fname; - } - - public String getLname() { - return lname; - } - - public void setLname(String lname) { - this.lname = lname; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((fname == null) ? 0 : fname.hashCode()); - result = prime * result + ((lname == null) ? 0 : lname.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Person other = (Person) obj; - if (fname == null) { - if (other.fname != null) { - return false; - } - } - else if (!fname.equals(other.fname)) { - return false; - } - if (lname == null) { - return other.lname == null; - } - else { - return lname.equals(other.lname); - } - } - - } - - protected abstract MessageStore getMessageStore(); - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageGroupStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageGroupStoreTests.java deleted file mode 100644 index 51efb6b6115..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageGroupStoreTests.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.List; -import java.util.Map; - -import org.assertj.core.api.InstanceOfAssertFactories; -import org.bson.Document; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.index.IndexInfo; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.channel.PriorityChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.store.AbstractMessageGroupStore; -import org.springframework.integration.store.MessageStore; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.StopWatch; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Amol Nayak - * @author Artem Bilan - * @author Artem Vozhdayenko - * @author Adama Sorho - * - */ -class ConfigurableMongoDbMessageGroupStoreTests extends AbstractMongoDbMessageGroupStoreTests { - - @Override - protected ConfigurableMongoDbMessageStore getMessageGroupStore() { - ConfigurableMongoDbMessageStore mongoDbMessageStore = - new ConfigurableMongoDbMessageStore(MONGO_DATABASE_FACTORY); - mongoDbMessageStore.setApplicationContext(this.testApplicationContext); - mongoDbMessageStore.afterPropertiesSet(); - return mongoDbMessageStore; - } - - @Override - protected MessageStore getMessageStore() { - return getMessageGroupStore(); - } - - @Test - void testWithAggregatorWithShutdown() { - super.testWithAggregatorWithShutdown("mongo-aggregator-configurable-config.xml"); - } - - @Test - void groupIsForceReleaseAfterTimeoutWhenGroupConditionIsSet() { - try (var context = new ClassPathXmlApplicationContext("mongo-aggregator-configurable-config.xml", getClass())) { - MessageChannel input = context.getBean("inputChannel", MessageChannel.class); - QueueChannel output = context.getBean("outputChannel", QueueChannel.class); - - Message message = MessageBuilder.withPayload("test") - .setSequenceNumber(1) - .setSequenceSize(10) - .setCorrelationId("test") - .build(); - - input.send(message); - - Message receive = output.receive(10_000); - - assertThat(receive) - .extracting("payload") - .asInstanceOf(InstanceOfAssertFactories.LIST) - .hasSize(1); - } - } - - @Test - @Disabled("The performance test. Enough slow. Also needs the release strategy changed to size() == 1000") - void messageGroupStoreLazyLoadPerformance() { - StopWatch watch = new StopWatch("Lazy-Load Performance"); - - int sequenceSize = 1000; - - performLazyLoadEagerTest(watch, sequenceSize, true); - - performLazyLoadEagerTest(watch, sequenceSize, false); - - // System. out .println(watch.prettyPrint()); // checkstyle - } - - private void performLazyLoadEagerTest(StopWatch watch, int sequenceSize, boolean lazyLoad) { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("mongo-aggregator-configurable-config.xml", getClass()); - context.refresh(); - - AbstractMessageGroupStore messageGroupStore = context.getBean("mongoStore", AbstractMessageGroupStore.class); - messageGroupStore.setLazyLoadMessageGroups(lazyLoad); - MessageChannel input = context.getBean("inputChannel", MessageChannel.class); - QueueChannel output = context.getBean("outputChannel", QueueChannel.class); - - watch.start(lazyLoad ? "Lazy-Load" : "Eager"); - - for (int i = 0; i < sequenceSize; i++) { - input.send(MessageBuilder.withPayload("" + i) - .setCorrelationId(1) - .build()); - } - - assertThat(output.receive(20000)).isNotNull(); - - watch.stop(); - - context.close(); - } - - @Test - void testWithCustomConverter() { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("ConfigurableMongoDbMessageStore-CustomConverter.xml", this - .getClass()); - context.refresh(); - - List indexesInfo = - new MongoTemplate(MONGO_DATABASE_FACTORY) - .indexOps("customConverterCollection") - .getIndexInfo(); - assertThat(indexesInfo).hasSize(0); - - TestGateway gateway = context.getBean(TestGateway.class); - String result = gateway.service("foo"); - assertThat(result).isEqualTo("FOO"); - context.close(); - } - - @Test - void testPriorityChannel() { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("ConfigurableMongoDbMessageStore-CustomConverter.xml", this - .getClass()); - context.refresh(); - - Object priorityChannel = context.getBean("priorityChannel"); - assertThat(priorityChannel).isInstanceOf(PriorityChannel.class); - - QueueChannel channel = (QueueChannel) priorityChannel; - - Message message = - MessageBuilder.withPayload("1") - .setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 1) - .build(); - channel.send(message); - message = MessageBuilder.withPayload("-1").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, -1).build(); - channel.send(message); - message = MessageBuilder.withPayload("3").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 3).build(); - channel.send(message); - message = MessageBuilder.withPayload("0").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 0).build(); - channel.send(message); - message = MessageBuilder.withPayload("2").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 2).build(); - channel.send(message); - message = MessageBuilder.withPayload("none").build(); - channel.send(message); - message = MessageBuilder.withPayload("31").setHeader(IntegrationMessageHeaderAccessor.PRIORITY, 3).build(); - channel.send(message); - - Message receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("3"); - - receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("31"); - - receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("2"); - - receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("1"); - - receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("0"); - - receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("-1"); - - receive = channel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("none"); - - context.close(); - } - - public interface TestGateway { - - String service(String payload); - - } - - @ReadingConverter - public static class MessageReadConverter implements Converter> { - - @Override - @SuppressWarnings("unchecked") - public GenericMessage convert(Document source) { - return new GenericMessage<>(source.get("payload"), (Map) source.get("headers")); - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStore-CustomConverter.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStore-CustomConverter.xml deleted file mode 100644 index ea96a3eacc8..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStore-CustomConverter.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStoreTests.java deleted file mode 100644 index 570c6aa9780..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/ConfigurableMongoDbMessageStoreTests.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import org.springframework.integration.store.MessageStore; - -/** - * @author Amol Nayak - * @author Artem Bilan - */ -public class ConfigurableMongoDbMessageStoreTests extends AbstractMongoDbMessageStoreTests { - - @Override - protected MessageStore getMessageStore() { - ConfigurableMongoDbMessageStore mongoDbMessageStore = - new ConfigurableMongoDbMessageStore(MONGO_DATABASE_FACTORY); - mongoDbMessageStore.setApplicationContext(this.testApplicationContext); - mongoDbMessageStore.afterPropertiesSet(); - return mongoDbMessageStore; - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationConfigurableTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationConfigurableTests-context.xml deleted file mode 100644 index 87d5e1e4ceb..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationConfigurableTests-context.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationTests-context.xml deleted file mode 100644 index 1b288ac5cab..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationTests-context.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationTests.java deleted file mode 100644 index 5aebdbc8be8..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/DelayerHandlerRescheduleIntegrationTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.Iterator; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.handler.DelayHandler; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 3.0 - */ -class DelayerHandlerRescheduleIntegrationTests implements MongoDbContainerTest { - - public static final String DELAYER_ID = "delayerWithMongoMS"; - - @Test - void testWithMongoDbMessageStore() throws Exception { - testDelayerHandlerRescheduleWithMongoDbMessageStore("DelayerHandlerRescheduleIntegrationTests-context.xml"); - } - - @Test - void testWithConfigurableMongoDbMessageStore() throws Exception { - testDelayerHandlerRescheduleWithMongoDbMessageStore("DelayerHandlerRescheduleIntegrationConfigurableTests-context.xml"); - } - - @SuppressWarnings("unchecked") - private void testDelayerHandlerRescheduleWithMongoDbMessageStore(String config) throws Exception { - AbstractApplicationContext context = new ClassPathXmlApplicationContext(config, this.getClass()); - MessageChannel input = context.getBean("input", MessageChannel.class); - MessageGroupStore messageStore = context.getBean("messageStore", MessageGroupStore.class); - - String delayerMessageGroupId = DELAYER_ID + ".messageGroupId"; - - messageStore.removeMessageGroup(delayerMessageGroupId); - - Message message1 = MessageBuilder.withPayload("test1").build(); - input.send(message1); - input.send(MessageBuilder.withPayload("test2").build()); - - // Emulate restart and check DB state before next start - // Interrupt taskScheduler as quickly as possible - ThreadPoolTaskScheduler taskScheduler = - (ThreadPoolTaskScheduler) IntegrationContextUtils.getTaskScheduler(context); - taskScheduler.shutdown(); - taskScheduler.getScheduledExecutor().awaitTermination(10, TimeUnit.SECONDS); - - assertThat(messageStore.messageGroupSize(delayerMessageGroupId)).isEqualTo(2); - - MessageGroup messageGroup = messageStore.getMessageGroup(delayerMessageGroupId); - Iterator> iterator = messageGroup.getMessages().iterator(); - Message messageInStore = iterator.next(); - Object payload = messageInStore.getPayload(); - - // INT-3049 - assertThat(payload).isInstanceOf(DelayHandler.DelayedMessageWrapper.class); - - Message original1 = (Message) ((DelayHandler.DelayedMessageWrapper) payload).getOriginal(); - messageInStore = iterator.next(); - Message original2 = (Message) ((DelayHandler.DelayedMessageWrapper) messageInStore.getPayload()) - .getOriginal(); - assertThat(message1).isIn(original1, original2); - - context.close(); - - context.refresh(); - - PollableChannel output = context.getBean("output", PollableChannel.class); - - Message message = output.receive(20000); - assertThat(message).isNotNull(); - - Object payload1 = message.getPayload(); - - message = output.receive(20000); - assertThat(message).isNotNull(); - Object payload2 = message.getPayload(); - assertThat(payload2).isNotSameAs(payload1); - - messageStore = context.getBean("messageStore", MessageGroupStore.class); - - assertThat(messageStore.messageGroupSize(delayerMessageGroupId)).isZero(); - context.close(); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageGroupStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageGroupStoreTests.java deleted file mode 100644 index f463974bf68..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageGroupStoreTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import org.junit.jupiter.api.Test; - -import org.springframework.integration.store.MessageStore; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - */ -class MongoDbMessageGroupStoreTests extends AbstractMongoDbMessageGroupStoreTests { - - @Override - protected MongoDbMessageStore getMessageGroupStore() { - MongoDbMessageStore mongoDbMessageStore = - new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - mongoDbMessageStore.setApplicationContext(testApplicationContext); - mongoDbMessageStore.afterPropertiesSet(); - return mongoDbMessageStore; - } - - @Override - protected MessageStore getMessageStore() { - return getMessageGroupStore(); - } - - @Test - void testWithAggregatorWithShutdown() { - super.testWithAggregatorWithShutdown("mongo-aggregator-config.xml"); - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreClaimCheckIntegrationTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreClaimCheckIntegrationTests.java deleted file mode 100644 index 56e37331cbd..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreClaimCheckIntegrationTests.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.io.Serializable; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.integration.mongodb.MongoDbContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.integration.transformer.ClaimCheckInTransformer; -import org.springframework.integration.transformer.ClaimCheckOutTransformer; -import org.springframework.messaging.Message; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Artem Bilan - * @author Artem Vozhdayenko - */ -class MongoDbMessageStoreClaimCheckIntegrationTests implements MongoDbContainerTest { - - static MongoDatabaseFactory MONGO_DATABASE_FACTORY; - - @BeforeAll - static void prepareMongoConnection() { - MONGO_DATABASE_FACTORY = MongoDbContainerTest.createMongoDbFactory(); - } - - private final GenericApplicationContext testApplicationContext = TestUtils.createTestApplicationContext(); - - @BeforeEach - public void setup() { - this.testApplicationContext.refresh(); - } - - @AfterEach - public void tearDown() { - this.testApplicationContext.close(); - } - - @Test - void stringPayload() { - MongoDbMessageStore messageStore = new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - messageStore.setApplicationContext(testApplicationContext); - messageStore.afterPropertiesSet(); - ClaimCheckInTransformer checkin = new ClaimCheckInTransformer(messageStore); - ClaimCheckOutTransformer checkout = new ClaimCheckOutTransformer(messageStore); - Message originalMessage = MessageBuilder.withPayload("test1").build(); - Message claimCheckMessage = checkin.transform(originalMessage); - assertThat(claimCheckMessage.getPayload()).isEqualTo(originalMessage.getHeaders().getId()); - Message checkedOutMessage = checkout.transform(claimCheckMessage); - assertThat(checkedOutMessage.getHeaders().getId()).isEqualTo(claimCheckMessage.getPayload()); - assertThat(checkedOutMessage.getPayload()).isEqualTo(originalMessage.getPayload()); - assertThat(checkedOutMessage).isEqualTo(originalMessage); - } - - @Test - void objectPayload() { - MongoDbMessageStore messageStore = new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - messageStore.setApplicationContext(testApplicationContext); - messageStore.afterPropertiesSet(); - ClaimCheckInTransformer checkin = new ClaimCheckInTransformer(messageStore); - ClaimCheckOutTransformer checkout = new ClaimCheckOutTransformer(messageStore); - Beverage payload = new Beverage(); - payload.setName("latte"); - payload.setShots(3); - payload.setIced(false); - Message originalMessage = MessageBuilder.withPayload(payload).build(); - Message claimCheckMessage = checkin.transform(originalMessage); - assertThat(claimCheckMessage.getPayload()).isEqualTo(originalMessage.getHeaders().getId()); - Message checkedOutMessage = checkout.transform(claimCheckMessage); - assertThat(checkedOutMessage.getPayload()).isEqualTo(originalMessage.getPayload()); - assertThat(checkedOutMessage.getHeaders().getId()).isEqualTo(claimCheckMessage.getPayload()); - assertThat(checkedOutMessage).isEqualTo(originalMessage); - } - - @Test - void stringPayloadConfigurable() { - ConfigurableMongoDbMessageStore messageStore = new ConfigurableMongoDbMessageStore(MONGO_DATABASE_FACTORY); - messageStore.setApplicationContext(this.testApplicationContext); - messageStore.afterPropertiesSet(); - ClaimCheckInTransformer checkin = new ClaimCheckInTransformer(messageStore); - ClaimCheckOutTransformer checkout = new ClaimCheckOutTransformer(messageStore); - Message originalMessage = MessageBuilder.withPayload("test1").build(); - Message claimCheckMessage = checkin.transform(originalMessage); - assertThat(claimCheckMessage.getPayload()).isEqualTo(originalMessage.getHeaders().getId()); - Message checkedOutMessage = checkout.transform(claimCheckMessage); - assertThat(checkedOutMessage.getHeaders().getId()).isEqualTo(claimCheckMessage.getPayload()); - assertThat(checkedOutMessage.getPayload()).isEqualTo(originalMessage.getPayload()); - assertThat(checkedOutMessage).isEqualTo(originalMessage); - } - - @Test - void objectPayloadConfigurable() { - ConfigurableMongoDbMessageStore messageStore = new ConfigurableMongoDbMessageStore(MONGO_DATABASE_FACTORY); - messageStore.setApplicationContext(this.testApplicationContext); - messageStore.afterPropertiesSet(); - ClaimCheckInTransformer checkin = new ClaimCheckInTransformer(messageStore); - ClaimCheckOutTransformer checkout = new ClaimCheckOutTransformer(messageStore); - Beverage payload = new Beverage(); - payload.setName("latte"); - payload.setShots(3); - payload.setIced(false); - Message originalMessage = MessageBuilder.withPayload(payload).build(); - Message claimCheckMessage = checkin.transform(originalMessage); - assertThat(claimCheckMessage.getPayload()).isEqualTo(originalMessage.getHeaders().getId()); - Message checkedOutMessage = checkout.transform(claimCheckMessage); - assertThat(checkedOutMessage.getPayload()).isEqualTo(originalMessage.getPayload()); - assertThat(checkedOutMessage.getHeaders().getId()).isEqualTo(claimCheckMessage.getPayload()); - assertThat(checkedOutMessage).isEqualTo(originalMessage); - } - - @SuppressWarnings("serial") - static class Beverage implements Serializable { - - private String name; - - private int shots; - - private boolean iced; - - @SuppressWarnings("unused") - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @SuppressWarnings("unused") - public int getShots() { - return shots; - } - - public void setShots(int shots) { - this.shots = shots; - } - - @SuppressWarnings("unused") - public boolean isIced() { - return iced; - } - - public void setIced(boolean iced) { - this.iced = iced; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (iced ? 1231 : 1237); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + shots; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Beverage other = (Beverage) obj; - if (iced != other.iced) { - return false; - } - if (name == null) { - if (other.name != null) { - return false; - } - } - else if (!name.equals(other.name)) { - return false; - } - return shots == other.shots; - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreTests.java deleted file mode 100644 index 90d203b2101..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mongodb.store; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.WritingConverter; -import org.springframework.integration.store.MessageStore; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Artem Vozhdayenko - * - */ -class MongoDbMessageStoreTests extends AbstractMongoDbMessageStoreTests { - - @Override - protected MessageStore getMessageStore() { - MongoDbMessageStore mongoDbMessageStore = new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - mongoDbMessageStore.setApplicationContext(testApplicationContext); - mongoDbMessageStore.afterPropertiesSet(); - return mongoDbMessageStore; - } - - @Test - void testCustomConverter() throws InterruptedException { - MongoDbMessageStore mongoDbMessageStore = new MongoDbMessageStore(MONGO_DATABASE_FACTORY); - FooToBytesConverter fooToBytesConverter = new FooToBytesConverter(); - mongoDbMessageStore.setCustomConverters(fooToBytesConverter); - mongoDbMessageStore.setApplicationContext(testApplicationContext); - mongoDbMessageStore.afterPropertiesSet(); - - mongoDbMessageStore.addMessage(new GenericMessage<>(new Foo("foo"))); - - assertThat(fooToBytesConverter.called.await(10, TimeUnit.SECONDS)).isTrue(); - } - - private static class Foo { - - String foo; - - Foo(String foo) { - this.foo = foo; - } - - @Override - public String toString() { - return foo; - } - - } - - @WritingConverter - private static class FooToBytesConverter implements Converter { - - private final CountDownLatch called = new CountDownLatch(1); - - @Override - public byte[] convert(Foo source) { - try { - return source.toString().getBytes(); - } - finally { - this.called.countDown(); - } - } - - } - -} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/mongo-aggregator-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/mongo-aggregator-config.xml deleted file mode 100644 index 29278e46250..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/mongo-aggregator-config.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/mongo-aggregator-configurable-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/mongo-aggregator-configurable-config.xml deleted file mode 100644 index 4a407d8429b..00000000000 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/mongo-aggregator-configurable-config.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mongodb/src/test/resources/log4j2-test.xml b/spring-integration-mongodb/src/test/resources/log4j2-test.xml deleted file mode 100644 index 21d30a22915..00000000000 --- a/spring-integration-mongodb/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/aot/MqttRuntimeHints.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/aot/MqttRuntimeHints.java deleted file mode 100644 index 066f9e63a05..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/aot/MqttRuntimeHints.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2024-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.aot; - -import java.util.stream.Stream; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -/** - * {@link RuntimeHintsRegistrar} for Spring Integration MQTT module. - * - * @author Artem Bilan - * - * @since 6.1.9 - */ -class MqttRuntimeHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - ReflectionHints reflectionHints = hints.reflection(); - // TODO until the real fix in Paho library. - Stream.of("org.eclipse.paho.client.mqttv3.MqttAsyncClient", "org.eclipse.paho.mqttv5.client.MqttAsyncClient") - .filter((typeName) -> ClassUtils.isPresent(typeName, classLoader)) - .map((typeName) -> loadClassByName(typeName, classLoader)) - .flatMap((type) -> Stream.ofNullable(ReflectionUtils.findMethod(type, "stopReconnectCycle"))) - .forEach(method -> reflectionHints.registerMethod(method, ExecutableMode.INVOKE)); - } - - private static Class loadClassByName(String typeName, @Nullable ClassLoader classLoader) { - try { - return ClassUtils.forName(typeName, classLoader); - } - catch (ClassNotFoundException ex) { - throw new IllegalArgumentException(ex); - } - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/aot/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/aot/package-info.java deleted file mode 100644 index 96ea1b000a1..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/aot/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes to support Spring AOT. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.aot; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParser.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParser.java deleted file mode 100644 index 08e12877bc3..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParser.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; - -/** - * The MqttAdapter Message Driven Channel adapter parser. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -public class MqttMessageDrivenChannelAdapterParser extends AbstractChannelAdapterParser { - - @Override - protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) { - - BeanDefinitionBuilder builder = BeanDefinitionBuilder - .genericBeanDefinition(MqttPahoMessageDrivenChannelAdapter.class); - - MqttParserUtils.parseCommon(element, builder, parserContext); - builder.addConstructorArgValue(element.getAttribute("topics")); - builder.addPropertyReference("outputChannel", channelName); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "qos"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "manual-acks"); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttNamespaceHandler.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttNamespaceHandler.java deleted file mode 100644 index c64a8b85b13..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttNamespaceHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.config.xml; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * The namespace handler for the MqttAdapter namespace. - * - * @author Gary Russell - * - * @since 4.0 - * - */ -public class MqttNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - @Override - public void init() { - this.registerBeanDefinitionParser("message-driven-channel-adapter", new MqttMessageDrivenChannelAdapterParser()); - this.registerBeanDefinitionParser("outbound-channel-adapter", new MqttOutboundChannelAdapterParser()); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParser.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParser.java deleted file mode 100644 index d2000be1f24..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParser.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.config.xml; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.util.StringUtils; - -/** - * The parser for the MqttAdapter Outbound Channel Adapter. - * - * @author Gary Russell - * @since 4.0 - * - */ -public class MqttOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected boolean shouldGenerateId() { - return false; - } - - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - - final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MqttPahoMessageHandler.class); - - MqttParserUtils.parseCommon(element, builder, parserContext); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-topic"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "topic-expression", - "topicExpressionString"); - if (StringUtils.hasText(element.getAttribute("converter")) && - (StringUtils.hasText(element.getAttribute("default-qos")) || - StringUtils.hasText(element.getAttribute("default-retained")))) { - parserContext.getReaderContext().error("If a 'converter' is provided, you cannot provide " + - "'default-qos' or 'default-retained'", element); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-qos"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "qos-expression", "qosExpressionString"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-retained"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "retained-expression", - "retainedExpressionString"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async-events"); - - return builder.getBeanDefinition(); - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java deleted file mode 100644 index 6594da1c974..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.config.xml; - -import java.util.Objects; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.util.StringUtils; - -/** - * Contains various utility methods for parsing Mqtt Adapter - * specific namespace elements as well as for the generation of the respective - * {@link org.springframework.beans.factory.config.BeanDefinition}s. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -final class MqttParserUtils { - - private MqttParserUtils() { - - } - - static void parseCommon(Element element, BeanDefinitionBuilder builder, ParserContext parserContext) { - ValueHolder holder; - int n = 0; - String url = element.getAttribute("url"); - if (StringUtils.hasText(url)) { - builder.addConstructorArgValue(url); - holder = builder.getRawBeanDefinition().getConstructorArgumentValues().getIndexedArgumentValues().get(n++); - Objects.requireNonNull(holder).setType("java.lang.String"); - } - builder.addConstructorArgValue(element.getAttribute("client-id")); - holder = builder.getRawBeanDefinition().getConstructorArgumentValues().getIndexedArgumentValues().get(n); - Objects.requireNonNull(holder).setType("java.lang.String"); - String clientFactory = element.getAttribute("client-factory"); - if (StringUtils.hasText(clientFactory)) { - builder.addConstructorArgReference(clientFactory); - } - else { - if (!StringUtils.hasText(url)) { - parserContext.getReaderContext().error("If no 'url' attribute is provided, a 'client-factory' " + - "(with serverURIs) is required", element); - } - builder.addConstructorArgValue(new RootBeanDefinition(DefaultMqttPahoClientFactory.class)); - } - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "converter"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/package-info.java deleted file mode 100644 index e9524439497..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides parser classes to provide Xml namespace support for the MqttAdapter components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.config.xml; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/AbstractMqttClientManager.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/AbstractMqttClientManager.java deleted file mode 100644 index 83001a825d1..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/AbstractMqttClientManager.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.SmartLifecycle; -import org.springframework.integration.mqtt.inbound.AbstractMqttMessageDrivenChannelAdapter; -import org.springframework.util.Assert; - -/** - * Abstract class for MQTT client managers which can be a base for any common v3/v5 client manager implementation. - * Contains some basic utility and implementation-agnostic fields and methods. - * - * @param MQTT client type - * @param MQTT connection options type (v5 or v3) - * - * @author Artem Vozhdayenko - * @author Artem Bilan - * @author Christian Tzolov - * - * @since 6.0 - */ -public abstract class AbstractMqttClientManager implements ClientManager, ApplicationEventPublisherAware { - - protected final Log logger = LogFactory.getLog(this.getClass()); // NOSONAR - - private static final int DEFAULT_MANAGER_PHASE = 0; - - protected final Lock lock = new ReentrantLock(); - - private final Set connectCallbacks = Collections.synchronizedSet(new HashSet<>()); - - private final String clientId; - - private int phase = DEFAULT_MANAGER_PHASE; - - private long completionTimeout = ClientManager.DEFAULT_COMPLETION_TIMEOUT; - - private long disconnectCompletionTimeout = ClientManager.DISCONNECT_COMPLETION_TIMEOUT; - - private long quiescentTimeout = ClientManager.QUIESCENT_TIMEOUT; - - private boolean manualAcks; - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - @SuppressWarnings("NullAway.Init") - private String url; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - private @Nullable T client; - - protected AbstractMqttClientManager(String clientId) { - Assert.notNull(clientId, "'clientId' is required"); - this.clientId = clientId; - } - - public void setManualAcks(boolean manualAcks) { - this.manualAcks = manualAcks; - } - - public String getUrl() { - return this.url; - } - - protected void setUrl(String url) { - this.url = url; - } - - public String getClientId() { - return this.clientId; - } - - protected ApplicationEventPublisher getApplicationEventPublisher() { - return this.applicationEventPublisher; - } - - protected void setClient(@Nullable T client) { - this.lock.lock(); - try { - this.client = client; - } - finally { - this.lock.unlock(); - } - } - - protected Set getCallbacks() { - return this.connectCallbacks; - } - - /** - * Set the completion timeout for operations. - * Default {@value #DEFAULT_COMPLETION_TIMEOUT} milliseconds. - * @param completionTimeout The timeout. - * @since 6.0.3 - */ - public void setCompletionTimeout(long completionTimeout) { - this.completionTimeout = completionTimeout; - } - - protected long getCompletionTimeout() { - return this.completionTimeout; - } - - /** - * Set the completion timeout when disconnecting. - * Default {@value #DISCONNECT_COMPLETION_TIMEOUT} milliseconds. - * @param completionTimeout The timeout. - * @since 6.0.3 - */ - public void setDisconnectCompletionTimeout(long completionTimeout) { - this.disconnectCompletionTimeout = completionTimeout; - } - - protected long getDisconnectCompletionTimeout() { - return this.disconnectCompletionTimeout; - } - - /** - * Set the quiescentTimeout timeout when disconnecting. - * Default is {@link ClientManager#QUIESCENT_TIMEOUT} milliseconds. - * @param quiescentTimeout The timeout. - * @since 7.0.0 - */ - public void setQuiescentTimeout(long quiescentTimeout) { - this.quiescentTimeout = quiescentTimeout; - } - - protected long getQuiescentTimeout() { - return this.quiescentTimeout; - } - - @Override - public boolean isManualAcks() { - return this.manualAcks; - } - - @Override - public @Nullable T getClient() { - this.lock.lock(); - try { - return this.client; - } - finally { - this.lock.unlock(); - } - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - Assert.notNull(applicationEventPublisher, "'applicationEventPublisher' cannot be null"); - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - public void setBeanName(String name) { - this.beanName = name; - } - - @Override - public String getBeanName() { - return this.beanName; - } - - /** - * The phase of component auto-start in {@link SmartLifecycle}. - * If the custom one is required, note that for the correct behavior it should be less than phase of - * {@link AbstractMqttMessageDrivenChannelAdapter} implementations. - * The default phase is {@link #DEFAULT_MANAGER_PHASE}. - * @return {@link SmartLifecycle} autostart phase - * @see #setPhase - */ - @Override - public int getPhase() { - return this.phase; - } - - @Override - public void addCallback(ConnectCallback connectCallback) { - this.connectCallbacks.add(connectCallback); - } - - @Override - public boolean removeCallback(ConnectCallback connectCallback) { - return this.connectCallbacks.remove(connectCallback); - } - - public boolean isRunning() { - this.lock.lock(); - try { - return this.client != null; - } - finally { - this.lock.unlock(); - } - } - - /** - * Set the phase of component autostart in {@link SmartLifecycle}. - * If the custom one is required, note that for the correct behavior it should be less than phase of - * {@link AbstractMqttMessageDrivenChannelAdapter} implementations. - * @see #getPhase - */ - public void setPhase(int phase) { - this.phase = phase; - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/ClientManager.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/ClientManager.java deleted file mode 100644 index 9deffc409f8..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/ClientManager.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.jspecify.annotations.Nullable; - -import org.springframework.context.SmartLifecycle; - -/** - * A utility abstraction over MQTT client which can be used in any MQTT-related component - * without need to handle generic client callbacks, reconnects etc. - * Using this manager in multiple MQTT integrations will preserve a single connection. - * - * @param MQTT client type - * @param MQTT connection options type (v5 or v3) - * - * @author Artem Vozhdayenko - * @author Artem Bilan - * @author Jiri Soucek - * @author Jiandong Ma - * - * @since 6.0 - */ -public interface ClientManager extends SmartLifecycle, MqttComponent { - - /** - * The default completion timeout in milliseconds. - */ - long DEFAULT_COMPLETION_TIMEOUT = 30_000L; - - Long QUIESCENT_TIMEOUT = 30_000L; - - /** - * The default disconnect completion timeout in milliseconds. - */ - long DISCONNECT_COMPLETION_TIMEOUT = 5_000L; - - /** - * Return the managed client. - * @return the managed client. - */ - @Nullable T getClient(); - - /** - * Return the managed client url. - * @return the managed client url, never null. - * @since 7.0 - */ - String getUrl(); - - /** - * Return the managed client id. - * @return the managed client id, never null. - * @since 7.0 - */ - String getClientId(); - - /** - * If manual acknowledge has to be used; false by default. - * @return true if manual acknowledge has to be used. - */ - boolean isManualAcks(); - - /** - * Register a callback for the {@code connectComplete} event from the client. - * @param connectCallback a {@link ConnectCallback} to register. - */ - void addCallback(ConnectCallback connectCallback); - - /** - * Remove the callback from registration. - * @param connectCallback a {@link ConnectCallback} to unregister. - * @return true if callback was removed. - */ - boolean removeCallback(ConnectCallback connectCallback); - - /** - * Return the managed clients isConnected. - * @return the managed clients isConnected. - * @since 6.4 - */ - boolean isConnected(); - - /** - * A contract for a custom callback on {@code connectComplete} event from the client. - * - * @see org.eclipse.paho.mqttv5.client.MqttCallback#connectComplete - * @see org.eclipse.paho.client.mqttv3.MqttCallbackExtended#connectComplete - */ - @FunctionalInterface - interface ConnectCallback { - - /** - * Called when the connection to the server is completed successfully. - * @param isReconnect if true, the connection was the result of automatic reconnect. - */ - void connectComplete(boolean isReconnect); - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/DefaultMqttPahoClientFactory.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/DefaultMqttPahoClientFactory.java deleted file mode 100644 index 4f8b2546aaf..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/DefaultMqttPahoClientFactory.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; -import org.eclipse.paho.client.mqttv3.IMqttClient; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttClient; -import org.eclipse.paho.client.mqttv3.MqttClientPersistence; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.jspecify.annotations.Nullable; - -import org.springframework.util.Assert; - -/** - * Creates a default {@link MqttClient} and a set of options as configured. - * - * @author Gary Russell - * @author Gunnar Hillert - * - * @since 4.0 - * - */ -public class DefaultMqttPahoClientFactory implements MqttPahoClientFactory { - - private MqttConnectOptions options = new MqttConnectOptions(); - - private @Nullable MqttClientPersistence persistence; - - /** - * Set the persistence to pass into the client constructor. - * @param persistence the persistence to set. - */ - public void setPersistence(MqttClientPersistence persistence) { - this.persistence = persistence; - } - - @Override - public IMqttClient getClientInstance(@Nullable String uri, String clientId) throws MqttException { - // Client validates URI even if overridden by options - return new MqttClient(uri == null ? "tcp://NO_URL_PROVIDED" : uri, clientId, this.persistence); - } - - @Override - public IMqttAsyncClient getAsyncClientInstance(@Nullable String uri, String clientId) throws MqttException { - // Client validates URI even if overridden by options - return new MqttAsyncClient(uri == null ? "tcp://NO_URL_PROVIDED" : uri, clientId, this.persistence); - } - - /** - * Set the preconfigured {@link MqttConnectOptions}. - * @param options the options. - * @since 4.3.16 - */ - public void setConnectionOptions(MqttConnectOptions options) { - Assert.notNull(options, "MqttConnectOptions cannot be null"); - this.options = options; - } - - @Override - public MqttConnectOptions getConnectionOptions() { - return this.options; - } - - public static class Will { - - private final String topic; - - private final byte[] payload; - - private final int qos; - - private final boolean retained; - - public Will(String topic, byte[] payload, int qos, boolean retained) { //NOSONAR - this.topic = topic; - this.payload = payload; //NOSONAR - this.qos = qos; - this.retained = retained; - } - - protected String getTopic() { - return this.topic; - } - - protected byte[] getPayload() { - return this.payload; //NOSONAR - } - - protected int getQos() { - return this.qos; - } - - protected boolean isRetained() { - return this.retained; - } - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttComponent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttComponent.java deleted file mode 100644 index dad26359dec..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttComponent.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.springframework.beans.factory.BeanNameAware; - -/** - * A component that interfaces with MQTT. - * - * @param The connection information type. - * - * @author Gary Russell - * @since 2.5 - * - */ -public interface MqttComponent extends BeanNameAware { - - /** - * Return this component's bean name. - * @return the bean name. - */ - String getBeanName(); - - /** - * Return information about the connection. - * @return the information. - */ - T getConnectionInfo(); - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttPahoClientFactory.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttPahoClientFactory.java deleted file mode 100644 index c58e079a89e..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttPahoClientFactory.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; -import org.eclipse.paho.client.mqttv3.IMqttClient; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.jspecify.annotations.Nullable; - -/** - * @author Gary Russell - * @since 4.0 - * - */ -public interface MqttPahoClientFactory { - - /** - * Retrieve a client instance. - * - * @param url The URL. - * @param clientId The client id. - * @return The client instance. - * @throws MqttException Any. - */ - IMqttClient getClientInstance(@Nullable String url, String clientId) throws MqttException; - - /** - * Retrieve an async client instance. - * - * @param url The URL. - * @param clientId The client id. - * @return The client instance. - * @throws MqttException Any. - * @since 4.1 - */ - IMqttAsyncClient getAsyncClientInstance(@Nullable String url, String clientId) throws MqttException; - - /** - * Retrieve the connection options. - * - * @return The options. - */ - MqttConnectOptions getConnectionOptions(); - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttPahoComponent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttPahoComponent.java deleted file mode 100644 index cfa91583fcb..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/MqttPahoComponent.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; - -/** - * An extension of {@link MqttComponent} for Eclipse Paho components. - * - * @author Gary Russell - * @since 5.4 - * - */ -public interface MqttPahoComponent extends MqttComponent { - - @Override - MqttConnectOptions getConnectionInfo(); - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/Mqttv3ClientManager.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/Mqttv3ClientManager.java deleted file mode 100644 index 74d5864abd0..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/Mqttv3ClientManager.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; -import org.eclipse.paho.client.mqttv3.MqttClientPersistence; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.support.MqttUtils; -import org.springframework.util.Assert; - -/** - * A client manager implementation for MQTT v3 protocol. Requires a client ID and server URI. - * If needed, the connection options may be overridden and passed as a {@link MqttConnectOptions} dependency. - * By default, automatic reconnect is used. If it is required to be turned off, one should listen for - * {@link MqttConnectionFailedEvent} and reconnect the MQTT client manually. - * - * @author Artem Vozhdayenko - * @author Artem Bilan - * @author Christian Tzolov - * @author Jiri Soucek - * - * @since 6.0 - */ -public class Mqttv3ClientManager - extends AbstractMqttClientManager - implements MqttCallbackExtended { - - private final MqttConnectOptions connectionOptions; - - private @Nullable MqttClientPersistence persistence; - - public Mqttv3ClientManager(String url, String clientId) { - this(buildDefaultConnectionOptions(url), clientId); - } - - @SuppressWarnings("this-escape") - public Mqttv3ClientManager(MqttConnectOptions connectionOptions, String clientId) { - super(clientId); - Assert.notNull(connectionOptions, "'connectionOptions' is required"); - this.connectionOptions = connectionOptions; - String[] serverURIs = connectionOptions.getServerURIs(); - Assert.notEmpty(serverURIs, "'serverURIs' must be provided in the 'MqttConnectionOptions'"); - setUrl(serverURIs[0]); - if (!connectionOptions.isAutomaticReconnect()) { - logger.info("If this `ClientManager` is used from message-driven channel adapters, " + - "it is recommended to set 'automaticReconnect' MQTT connection option. " + - "Otherwise connection check and reconnect should be done manually."); - } - } - - private static MqttConnectOptions buildDefaultConnectionOptions(String url) { - Assert.notNull(url, "'url' is required"); - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setServerURIs(new String[] {url}); - connectOptions.setAutomaticReconnect(true); - return connectOptions; - } - - /** - * Set the {@link MqttClientPersistence} for a client. - * @param persistence persistence implementation to use for te client - */ - public void setPersistence(MqttClientPersistence persistence) { - this.persistence = persistence; - } - - @Override - public MqttConnectOptions getConnectionInfo() { - return this.connectionOptions; - } - - @Override - public void start() { - this.lock.lock(); - try { - var client = getClient(); - if (client == null) { - try { - client = createClient(); - } - catch (MqttException e) { - throw new IllegalStateException("could not start client manager", e); - } - } - setClient(client); - try { - client.connect(this.connectionOptions).waitForCompletion(getCompletionTimeout()); - } - catch (MqttException ex) { - // See GH-3822 - if (this.connectionOptions.isAutomaticReconnect()) { - try { - client.reconnect(); - } - catch (MqttException re) { - logger.error("MQTT client failed to connect. Never happens.", re); - } - } - else { - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, ex)); - logger.error("Could not start client manager, client_id=" + getClientId(), ex); - } - } - } - finally { - this.lock.unlock(); - } - } - - private IMqttAsyncClient createClient() throws MqttException { - var url = getUrl(); - var clientId = getClientId(); - var client = new MqttAsyncClient(url, clientId, this.persistence); - client.setManualAcks(isManualAcks()); - client.setCallback(this); - return client; - } - - @Override - public void stop() { - this.lock.lock(); - try { - var client = getClient(); - if (client == null) { - return; - } - try { - client.disconnectForcibly(getQuiescentTimeout(), getDisconnectCompletionTimeout()); - if (getConnectionInfo().isAutomaticReconnect()) { - MqttUtils.stopClientReconnectCycle(client); - } - } - catch (MqttException e) { - logger.error("Could not disconnect from the client", e); - } - finally { - try { - client.close(); - } - catch (MqttException e) { - logger.error("Could not close the client", e); - } - setClient(null); - } - } - finally { - this.lock.unlock(); - } - } - - @Override - public void connectionLost(Throwable cause) { - this.lock.lock(); - try { - logger.error("Connection lost, client_id=" + getClientId(), cause); - } - finally { - this.lock.unlock(); - } - } - - @Override - public void connectComplete(boolean reconnect, String serverURI) { - getCallbacks().forEach(callback -> callback.connectComplete(reconnect)); - } - - @Override - public void messageArrived(String topic, MqttMessage message) { - // not this manager concern - } - - @Override - public void deliveryComplete(IMqttDeliveryToken token) { - // nor this manager concern - } - - @Override - public boolean isConnected() { - this.lock.lock(); - try { - IMqttAsyncClient client = getClient(); - if (client != null) { - return client.isConnected(); - } - return false; - } - finally { - this.lock.unlock(); - } - - } -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/Mqttv5ClientManager.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/Mqttv5ClientManager.java deleted file mode 100644 index b50bf499511..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/Mqttv5ClientManager.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.core; - -import org.eclipse.paho.mqttv5.client.IMqttAsyncClient; -import org.eclipse.paho.mqttv5.client.IMqttToken; -import org.eclipse.paho.mqttv5.client.MqttAsyncClient; -import org.eclipse.paho.mqttv5.client.MqttCallback; -import org.eclipse.paho.mqttv5.client.MqttClientPersistence; -import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; -import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse; -import org.eclipse.paho.mqttv5.common.MqttException; -import org.eclipse.paho.mqttv5.common.MqttMessage; -import org.eclipse.paho.mqttv5.common.packet.MqttProperties; -import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.support.MqttUtils; -import org.springframework.util.Assert; - -/** - * A client manager implementation for MQTT v5 protocol. Requires a client ID and server URI. - * If needed, the connection options may be overridden and passed as a {@link MqttConnectionOptions} dependency. - * By default, automatic reconnect is used. If it is required to be turned off, one should listen for - * {@link MqttConnectionFailedEvent} and reconnect the MQTT client manually. - * - * @author Artem Vozhdayenko - * @author Artem Bilan - * @author Christian Tzolov - * @author Jiri Soucek - * - * @since 6.0 - */ -public class Mqttv5ClientManager - extends AbstractMqttClientManager - implements MqttCallback { - - private final MqttConnectionOptions connectionOptions; - - private @Nullable MqttClientPersistence persistence; - - public Mqttv5ClientManager(String url, String clientId) { - this(buildDefaultConnectionOptions(url), clientId); - } - - @SuppressWarnings("this-escape") - public Mqttv5ClientManager(MqttConnectionOptions connectionOptions, String clientId) { - super(clientId); - Assert.notNull(connectionOptions, "'connectionOptions' is required"); - this.connectionOptions = connectionOptions; - if (!this.connectionOptions.isAutomaticReconnect()) { - logger.info("If this `ClientManager` is used from message-driven channel adapters, " + - "it is recommended to set 'automaticReconnect' MQTT connection option. " + - "Otherwise connection check and reconnect should be done manually."); - } - Assert.notEmpty(connectionOptions.getServerURIs(), - "'serverURIs' must be provided in the 'MqttConnectionOptions'"); - setUrl(connectionOptions.getServerURIs()[0]); - } - - private static MqttConnectionOptions buildDefaultConnectionOptions(String url) { - Assert.notNull(url, "'url' is required"); - var connectionOptions = new MqttConnectionOptions(); - connectionOptions.setServerURIs(new String[] {url}); - connectionOptions.setAutomaticReconnect(true); - return connectionOptions; - } - - /** - * Set the {@link org.eclipse.paho.client.mqttv3.MqttClientPersistence} for a client. - * @param persistence persistence implementation to use for te client - */ - public void setPersistence(MqttClientPersistence persistence) { - this.persistence = persistence; - } - - @Override - public MqttConnectionOptions getConnectionInfo() { - return this.connectionOptions; - } - - @Override - public void start() { - this.lock.lock(); - try { - var client = getClient(); - if (client == null) { - try { - client = createClient(); - } - catch (MqttException e) { - throw new IllegalStateException("Could not start client manager", e); - } - } - setClient(client); - try { - client.connect(this.connectionOptions).waitForCompletion(getCompletionTimeout()); - } - catch (MqttException ex) { - if (this.connectionOptions.isAutomaticReconnect()) { - try { - client.reconnect(); - } - catch (MqttException re) { - logger.error("MQTT client failed to connect. Never happens.", re); - } - } - else { - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, ex)); - logger.error("Could not start client manager, client_id=" + getClientId(), ex); - } - } - } - finally { - this.lock.unlock(); - } - } - - private MqttAsyncClient createClient() throws MqttException { - var url = getUrl(); - var clientId = getClientId(); - var client = new MqttAsyncClient(url, clientId, this.persistence); - client.setManualAcks(isManualAcks()); - client.setCallback(this); - return client; - } - - @Override - public void stop() { - this.lock.lock(); - try { - var client = getClient(); - if (client == null) { - return; - } - - try { - client.disconnectForcibly(getQuiescentTimeout(), getDisconnectCompletionTimeout(), - MqttReturnCode.RETURN_CODE_SUCCESS, new MqttProperties()); - if (getConnectionInfo().isAutomaticReconnect()) { - MqttUtils.stopClientReconnectCycle(client); - } - } - catch (MqttException e) { - logger.error("Could not disconnect from the client", e); - } - finally { - try { - client.close(); - } - catch (MqttException e) { - logger.error("Could not close the client", e); - } - setClient(null); - } - } - finally { - this.lock.unlock(); - } - } - - @Override - public void messageArrived(String topic, MqttMessage message) { - // not this manager concern - } - - @Override - public void deliveryComplete(IMqttToken token) { - // not this manager concern - } - - @Override - public void connectComplete(boolean reconnect, String serverURI) { - getCallbacks().forEach(callback -> callback.connectComplete(reconnect)); - } - - @Override - public void authPacketArrived(int reasonCode, MqttProperties properties) { - // not this manager concern - } - - @Override - public void disconnected(MqttDisconnectResponse disconnectResponse) { - if (logger.isInfoEnabled()) { - logger.info("MQTT disconnected: " + disconnectResponse); - } - } - - @Override - public void mqttErrorOccurred(MqttException exception) { - logger.error("MQTT error occurred", exception); - } - - @Override - public boolean isConnected() { - this.lock.lock(); - try { - IMqttAsyncClient client = getClient(); - if (client != null) { - return client.isConnected(); - } - return false; - } - finally { - this.lock.unlock(); - } - } -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/package-info.java deleted file mode 100644 index 22db3e8593d..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/core/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides core classes of the MqttAdapter module. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.core; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttConnectionFailedEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttConnectionFailedEvent.java deleted file mode 100644 index 580bbfd31ef..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttConnectionFailedEvent.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -import org.jspecify.annotations.Nullable; - -/** - * The {@link MqttIntegrationEvent} to notify about lost connection to the server. - * When normal disconnection is happened (initiated by the server), the {@code cause} is null. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.2.2 - * - */ -@SuppressWarnings("serial") -public class MqttConnectionFailedEvent extends MqttIntegrationEvent { - - public MqttConnectionFailedEvent(Object source) { - super(source); - } - - public MqttConnectionFailedEvent(Object source, @Nullable Throwable cause) { - super(source, cause); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttIntegrationEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttIntegrationEvent.java deleted file mode 100644 index 45f226b05c7..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttIntegrationEvent.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.events.IntegrationEvent; - -/** - * Base class for Mqtt Events. For {@link #getSourceAsType()}, you should use a subtype - * of {@link org.springframework.integration.mqtt.core.MqttComponent} for the receiving - * variable. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.1 - */ -@SuppressWarnings("serial") -public abstract class MqttIntegrationEvent extends IntegrationEvent { - - public MqttIntegrationEvent(Object source) { - super(source); - } - - public MqttIntegrationEvent(Object source, @Nullable Throwable cause) { - super(source, cause); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageDeliveredEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageDeliveredEvent.java deleted file mode 100644 index dc2398bb7cc..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageDeliveredEvent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -/** - * An event emitted (when using aysnc) when the client indicates the message - * was delivered. - * - * @author Gary Russell - * @since 4.1 - * - */ -@SuppressWarnings("serial") -public class MqttMessageDeliveredEvent extends MqttMessageDeliveryEvent { - - public MqttMessageDeliveredEvent(Object source, int messageId, String clientId, - int clientInstance) { - super(source, messageId, clientId, clientInstance); - } - - @Override - public String toString() { - return "MqttMessageDeliveredEvent [clientId=" + getClientId() - + ", clientInstance=" + getClientInstance() - + ", messageId=" + getMessageId() - + "]"; - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageDeliveryEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageDeliveryEvent.java deleted file mode 100644 index 247d779cfc3..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageDeliveryEvent.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -/** - * Base class for events related to message delivery. Properties {@link #messageId}, - * {@link #clientId} and {@link #clientInstance} can be used to correlate events. - * - * @author Gary Russell - * @since 4.1 - * - */ -@SuppressWarnings("serial") -public abstract class MqttMessageDeliveryEvent extends MqttIntegrationEvent { - - private final int messageId; - - private final String clientId; - - private final int clientInstance; - - public MqttMessageDeliveryEvent(Object source, int messageId, String clientId, int clientInstance) { - super(source); - this.messageId = messageId; - this.clientId = clientId; - this.clientInstance = clientInstance; - } - - public int getMessageId() { - return this.messageId; - } - - public String getClientId() { - return this.clientId; - } - - public int getClientInstance() { - return this.clientInstance; - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageNotDeliveredEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageNotDeliveredEvent.java deleted file mode 100644 index 78ee48b3659..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageNotDeliveredEvent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -import java.io.Serial; - -/** - * An event emitted (when using aysnc) when the client indicates the message - * was not delivered on publish operation. - * - * @author Artem Bilan - * - * @since 6.4 - * - */ -public class MqttMessageNotDeliveredEvent extends MqttMessageDeliveryEvent { - - @Serial - private static final long serialVersionUID = 8983514811627569920L; - - private final Throwable exception; - - public MqttMessageNotDeliveredEvent(Object source, int messageId, String clientId, - int clientInstance, Throwable exception) { - - super(source, messageId, clientId, clientInstance); - this.exception = exception; - } - - public Throwable getException() { - return this.exception; - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageSentEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageSentEvent.java deleted file mode 100644 index 5203d5ae6fa..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttMessageSentEvent.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -import org.springframework.messaging.Message; - -/** - * An event emitted (when using async) when the client indicates that a message - * has been sent. - * - * @author Gary Russell - * @since 4.1 - * - */ -@SuppressWarnings("serial") -public class MqttMessageSentEvent extends MqttMessageDeliveryEvent { - - private final Message message; - - private final String topic; - - public MqttMessageSentEvent(Object source, Message message, String topic, int messageId, - String clientId, int clientInstance) { - super(source, messageId, clientId, clientInstance); - this.message = message; - this.topic = topic; - } - - public Message getMessage() { - return this.message; - } - - public String getTopic() { - return this.topic; - } - - @Override - public String toString() { - return "MqttMessageSentEvent [message=" + this.message - + ", topic=" + this.topic - + ", clientId=" + getClientId() - + ", clientInstance=" + getClientInstance() - + ", messageId=" + getMessageId() - + "]"; - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttProtocolErrorEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttProtocolErrorEvent.java deleted file mode 100644 index 2aa0b81268d..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttProtocolErrorEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -import org.eclipse.paho.mqttv5.common.MqttException; - -/** - * The even representing an MQTT error occurred during client interaction. - * - * @author Artem Bilan - * - * @since 5.5.5 - * - * @see org.eclipse.paho.mqttv5.client.MqttCallback#mqttErrorOccurred(MqttException) - */ -@SuppressWarnings("serial") -public class MqttProtocolErrorEvent extends MqttIntegrationEvent { - - public MqttProtocolErrorEvent(Object source, MqttException exception) { - super(source, exception); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttSubscribedEvent.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttSubscribedEvent.java deleted file mode 100644 index 646faadbd15..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/MqttSubscribedEvent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.event; - -/** - * @author Gary Russell - * @since 4.2.2 - * - */ -@SuppressWarnings("serial") -public class MqttSubscribedEvent extends MqttIntegrationEvent { - - private final String message; - - public MqttSubscribedEvent(Object source, String message) { - super(source); - this.message = message; - } - - public String getMessage() { - return this.message; - } - - @Override - public String toString() { - return "MqttSubscribedEvent [message=" + this.message + ", source=" + source + "]"; - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/package-info.java deleted file mode 100644 index 4555bd7eb20..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/event/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * ApplicationEvents generated by the mqtt module. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.event; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java deleted file mode 100644 index 107ec7c29ea..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.inbound; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.jspecify.annotations.Nullable; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.log.LogMessage; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.mqtt.core.ClientManager; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.support.management.IntegrationManagedResource; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.messaging.MessagingException; -import org.springframework.util.Assert; - -/** - * Abstract class for MQTT Message-Driven Channel Adapters. - * - * @param MQTT Client type - * @param MQTT connection options type (v5 or v3) - * - * @author Gary Russell - * @author Artem Bilan - * @author Trung Pham - * @author Mikhail Polivakha - * @author Artem Vozhdayenko - * @author Jiri Soucek - * @author Glenn Renfro - * - * @since 4.0 - * - */ -@ManagedResource -@IntegrationManagedResource -public abstract class AbstractMqttMessageDrivenChannelAdapter extends MessageProducerSupport - implements ApplicationEventPublisherAware, ClientManager.ConnectCallback { - - protected final Lock topicLock = new ReentrantLock(); // NOSONAR - - private final @Nullable String url; - - private final String clientId; - - private final Map topics; - - private final @Nullable ClientManager clientManager; - - private long completionTimeout = ClientManager.DEFAULT_COMPLETION_TIMEOUT; - - private long disconnectCompletionTimeout = ClientManager.DISCONNECT_COMPLETION_TIMEOUT; - - private long quiescentTimeout = ClientManager.QUIESCENT_TIMEOUT; - - private boolean manualAcks; - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - private @Nullable MqttMessageConverter converter; - - public AbstractMqttMessageDrivenChannelAdapter(@Nullable String url, String clientId, String... topic) { - Assert.hasText(clientId, "'clientId' cannot be null or empty"); - this.url = url; - this.clientId = clientId; - this.topics = initTopics(topic); - this.clientManager = null; - } - - public AbstractMqttMessageDrivenChannelAdapter(ClientManager clientManager, String... topic) { - Assert.notNull(clientManager, "'clientManager' cannot be null"); - this.clientManager = clientManager; - this.topics = initTopics(topic); - this.url = clientManager.getUrl(); - this.clientId = clientManager.getClientId(); - } - - private static Map initTopics(String[] topics) { - validateTopics(topics); - - return Arrays.stream(topics) - .collect(Collectors.toMap(Function.identity(), (key) -> 1, (x, y) -> y, LinkedHashMap::new)); - } - - private static void validateTopics(String[] topics) { - Assert.notNull(topics, "'topics' cannot be null"); - Assert.noNullElements(topics, "'topics' cannot have null elements"); - - for (String topic : topics) { - Assert.hasText(topic, "The topic to subscribe cannot be empty string"); - } - } - - public void setConverter(MqttMessageConverter converter) { - Assert.notNull(converter, "'converter' cannot be null"); - this.converter = converter; - } - - @Nullable - protected ClientManager getClientManager() { - return this.clientManager; - } - - /** - * Set the QoS for each topic; a single value will apply to all topics otherwise - * the correct number of qos values must be provided. - * @param qos The qos value(s). - * @since 4.1 - */ - public void setQos(int... qos) { - Assert.notNull(qos, "'qos' cannot be null"); - if (qos.length == 1) { - for (Map.Entry topic : this.topics.entrySet()) { - topic.setValue(qos[0]); - } - } - else { - Assert.isTrue(qos.length == this.topics.size(), - "When setting qos, the array must be the same length as the topics"); - int n = 0; - for (Map.Entry topic : this.topics.entrySet()) { - topic.setValue(qos[n++]); - } - } - } - - @ManagedAttribute - public int[] getQos() { - this.topicLock.lock(); - try { - int[] topicQos = new int[this.topics.size()]; - int n = 0; - for (int qos : this.topics.values()) { - topicQos[n++] = qos; - } - return topicQos; - } - finally { - this.topicLock.unlock(); - } - } - - @Nullable - protected String getUrl() { - return this.url; - } - - protected String getClientId() { - return this.clientId; - } - - protected @Nullable MqttMessageConverter getConverter() { - return this.converter; - } - - @ManagedAttribute - public String[] getTopic() { - this.topicLock.lock(); - try { - return this.topics.keySet().toArray(new String[0]); - } - finally { - this.topicLock.unlock(); - } - } - - /** - * Set the completion timeout when disconnecting. - * Default {@value ClientManager#DISCONNECT_COMPLETION_TIMEOUT} milliseconds. - * @param completionTimeout The timeout. - * @since 5.1.10 - */ - public void setDisconnectCompletionTimeout(long completionTimeout) { - this.disconnectCompletionTimeout = completionTimeout; - } - - protected long getDisconnectCompletionTimeout() { - return this.disconnectCompletionTimeout; - } - - /** - * Set the quiescentTimeout timeout when disconnecting. - * Default is {@link ClientManager#QUIESCENT_TIMEOUT} milliseconds. - * @param quiescentTimeout The timeout. - * @since 7.0.0 - */ - public void setQuiescentTimeout(long quiescentTimeout) { - this.quiescentTimeout = quiescentTimeout; - } - - protected long getQuiescentTimeout() { - return this.quiescentTimeout; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.clientManager != null) { - this.clientManager.addCallback(this); - if (this.clientManager.isConnected()) { - connectComplete(false); - } - } - } - - @Override - public void destroy() { - super.destroy(); - if (this.clientManager != null) { - this.clientManager.removeCallback(this); - } - } - - @Override - public String getComponentType() { - return "mqtt:inbound-channel-adapter"; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; // NOSONAR (inconsistent synchronization) - } - - protected ApplicationEventPublisher getApplicationEventPublisher() { - return this.applicationEventPublisher; - } - - /** - * Set the acknowledgment mode to manual. - * @param manualAcks true for manual acks. - * @since 5.3 - */ - public void setManualAcks(boolean manualAcks) { - this.manualAcks = manualAcks; - } - - protected boolean isManualAcks() { - return this.clientManager == null ? this.manualAcks : this.clientManager.isManualAcks(); - } - - /** - * Set the completion timeout for operations. - * Default {@value ClientManager#DEFAULT_COMPLETION_TIMEOUT} milliseconds. - * @param completionTimeout The timeout. - * @since 4.1 - */ - public void setCompletionTimeout(long completionTimeout) { - this.completionTimeout = completionTimeout; - } - - protected long getCompletionTimeout() { - return this.completionTimeout; - } - - /** - * Add a topic to the subscribed list. - * @param topic The topic. - * @param qos The qos. - * @throws MessagingException if the topic is already in the list. - * @since 4.1 - */ - @ManagedOperation - public void addTopic(String topic, int qos) { - validateTopics(new String[] {topic}); - this.topicLock.lock(); - try { - if (this.topics.containsKey(topic)) { - throw new MessagingException("Topic '" + topic + "' is already subscribed."); - } - this.topics.put(topic, qos); - logger.debug(LogMessage.format("Added '%s' to subscriptions.", topic)); - } - finally { - this.topicLock.unlock(); - } - } - - /** - * Add a topic (or topics) to the subscribed list (qos=1). - * @param topics The topics. - * @throws MessagingException if the topics is already in the list. - * @since 4.1 - */ - @ManagedOperation - public void addTopic(String... topics) { - validateTopics(topics); - this.topicLock.lock(); - try { - for (String t : topics) { - addTopic(t, 1); - } - } - finally { - this.topicLock.unlock(); - } - } - - /** - * Add topics to the subscribed list. - * @param topics The topics. - * @param qos The qos for each topic. - * @throws MessagingException if a topics is already in the list. - * @since 4.1 - */ - @ManagedOperation - public void addTopics(String[] topics, int[] qos) { - validateTopics(topics); - Assert.isTrue(topics.length == qos.length, "topics and qos arrays must the be the same length."); - this.topicLock.lock(); - try { - for (String newTopic : topics) { - if (this.topics.containsKey(newTopic)) { - throw new MessagingException("Topic '" + newTopic + "' is already subscribed."); - } - } - for (int i = 0; i < topics.length; i++) { - addTopic(topics[i], qos[i]); - } - } - finally { - this.topicLock.unlock(); - } - } - - /** - * Remove a topic (or topics) from the subscribed list. - * @param topic The topic. - * @throws MessagingException if the topic is not in the list. - * @since 4.1 - */ - @ManagedOperation - public void removeTopic(String... topic) { - this.topicLock.lock(); - try { - for (String name : topic) { - if (this.topics.remove(name) != null) { - logger.debug(LogMessage.format("Removed '%s' from subscriptions.", name)); - } - } - } - finally { - this.topicLock.unlock(); - } - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/MqttPahoMessageDrivenChannelAdapter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/MqttPahoMessageDrivenChannelAdapter.java deleted file mode 100644 index b2f50c5bd9f..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/MqttPahoMessageDrivenChannelAdapter.java +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.inbound; - -import java.util.Arrays; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Stream; - -import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.IMqttMessageListener; -import org.eclipse.paho.client.mqttv3.IMqttToken; -import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.jspecify.annotations.Nullable; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.acks.SimpleAcknowledgment; -import org.springframework.integration.mqtt.core.ClientManager; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.core.MqttPahoClientFactory; -import org.springframework.integration.mqtt.core.MqttPahoComponent; -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; -import org.springframework.integration.mqtt.support.MqttUtils; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.converter.MessageConversionException; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.Assert; - -/** - * Eclipse Paho Implementation. When consuming {@link org.springframework.integration.mqtt.event.MqttIntegrationEvent}s - * published by this component use {@code MqttPahoComponent adapter = event.getSourceAsType()} to get a - * reference, allowing you to obtain the bean name and {@link MqttConnectOptions}. This - * technique allows consumption of events from both inbound and outbound endpoints in the - * same event listener. - * - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * @author Glenn Renfro - * - * @since 4.0 - * - */ -public class MqttPahoMessageDrivenChannelAdapter - extends AbstractMqttMessageDrivenChannelAdapter - implements MqttCallbackExtended, MqttPahoComponent { - - private final Lock lock = new ReentrantLock(); - - private final MqttPahoClientFactory clientFactory; - - @SuppressWarnings("NullAway.Init") - private volatile IMqttAsyncClient client; - - private volatile boolean readyToSubscribeOnStart; - - /** - * Use this constructor when you don't need additional {@link MqttConnectOptions}. - * @param url The URL. - * @param clientId The client id. - * @param topic The topic(s). - */ - public MqttPahoMessageDrivenChannelAdapter(String url, String clientId, String... topic) { - this(url, clientId, new DefaultMqttPahoClientFactory(), topic); - } - - /** - * Use this constructor for a single url (although it may be overridden if the server - * URI(s) are provided by the {@link MqttConnectOptions#getServerURIs()} provided by - * the {@link MqttPahoClientFactory}). - * @param url the URL. - * @param clientId The client id. - * @param clientFactory The client factory. - * @param topic The topic(s). - */ - public MqttPahoMessageDrivenChannelAdapter(String url, String clientId, MqttPahoClientFactory clientFactory, - String... topic) { - - super(url, clientId, topic); - this.clientFactory = clientFactory; - } - - /** - * Use this constructor if the server URI(s) are provided by the - * {@link MqttConnectOptions#getServerURIs()} provided by the - * {@link MqttPahoClientFactory}. - * @param clientId The client id. - * @param clientFactory The client factory. - * @param topic The topic(s). - * @since 4.1 - */ - public MqttPahoMessageDrivenChannelAdapter(String clientId, MqttPahoClientFactory clientFactory, - String... topic) { - - super(null, clientId, topic); - this.clientFactory = clientFactory; - } - - /** - * Use this constructor when you need to use a single {@link ClientManager} - * (for instance, to reuse an MQTT connection). - * @param clientManager The client manager. - * @param topic The topic(s). - * @since 6.0 - */ - public MqttPahoMessageDrivenChannelAdapter(ClientManager clientManager, - String... topic) { - - super(clientManager, topic); - var factory = new DefaultMqttPahoClientFactory(); - factory.setConnectionOptions(clientManager.getConnectionInfo()); - this.clientFactory = factory; - } - - @Override - public MqttConnectOptions getConnectionInfo() { - MqttConnectOptions options = this.clientFactory.getConnectionOptions(); - if (options.getServerURIs() == null) { - String url = getUrl(); - if (url != null) { - options = MqttUtils.cloneConnectOptions(options); - options.setServerURIs(new String[] {url}); - } - } - return options; - } - - @Override - protected void onInit() { - super.onInit(); - if (getConverter() == null) { - DefaultPahoMessageConverter pahoMessageConverter = new DefaultPahoMessageConverter(); - pahoMessageConverter.setBeanFactory(getBeanFactory()); - setConverter(pahoMessageConverter); - } - } - - @Override - protected void doStart() { - try { - connect(); - if (this.readyToSubscribeOnStart) { - subscribe(); - } - } - catch (Exception ex) { - if (getConnectionInfo().isAutomaticReconnect()) { - try { - this.client.reconnect(); - } - catch (MqttException re) { - logger.error(re, "MQTT client failed to connect. Never happens."); - } - } - else { - logger.error(ex, "Exception while connecting"); - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, ex)); - } - } - } - - @SuppressWarnings("deprecation") - private void connect() throws MqttException { - this.lock.lock(); - try { - MqttConnectOptions connectionOptions = this.clientFactory.getConnectionOptions(); - var clientManager = getClientManager(); - if (clientManager == null) { - Assert.state(getUrl() != null || connectionOptions.getServerURIs() != null, - "If no 'url' provided, connectionOptions.getServerURIs() must not be null"); - this.client = this.clientFactory.getAsyncClientInstance(getUrl(), getClientId()); - this.client.setCallback(this); - this.client.connect(connectionOptions).waitForCompletion(getCompletionTimeout()); - this.client.setManualAcks(isManualAcks()); - } - else { - IMqttAsyncClient theClient = clientManager.getClient(); - Assert.state(theClient != null, "The 'client' must not be null, consider to start the 'clientManager'."); - this.client = theClient; - } - } - finally { - this.lock.unlock(); - } - } - - @Override - protected void doStop() { - this.lock.lock(); - try { - this.readyToSubscribeOnStart = false; - try { - if (this.clientFactory.getConnectionOptions().isCleanSession()) { - this.client.unsubscribe(getTopic()); - // Have to re-subscribe on next start if connection is not lost. - this.readyToSubscribeOnStart = true; - - } - } - catch (MqttException ex1) { - logger.error(ex1, "Exception while unsubscribing"); - } - - if (getClientManager() != null) { - return; - } - - try { - this.client.disconnectForcibly(getQuiescentTimeout(), getDisconnectCompletionTimeout()); - if (getConnectionInfo().isAutomaticReconnect()) { - MqttUtils.stopClientReconnectCycle(this.client); - } - } - catch (MqttException ex) { - logger.error(ex, "Exception while disconnecting"); - } - } - finally { - this.lock.unlock(); - } - } - - @Override - public void destroy() { - super.destroy(); - if (getClientManager() == null) { - try { - this.client.close(); - } - catch (MqttException e) { - logger.error(e, "Could not close client"); - } - } - } - - @Override - public void addTopic(String topic, int qos) { - this.topicLock.lock(); - try { - super.addTopic(topic, qos); - if (this.client != null && this.client.isConnected()) { - this.client.subscribe(topic, qos, this::messageArrived) - .waitForCompletion(getCompletionTimeout()); - } - } - catch (MqttException e) { - super.removeTopic(topic); - throw new MessagingException("Failed to subscribe to topic " + topic, e); - } - finally { - this.topicLock.unlock(); - } - } - - @Override - public void removeTopic(String... topic) { - this.topicLock.lock(); - try { - if (this.client != null && this.client.isConnected()) { - this.client.unsubscribe(topic).waitForCompletion(getCompletionTimeout()); - } - super.removeTopic(topic); - } - catch (MqttException e) { - throw new MessagingException("Failed to unsubscribe from topic(s) " + Arrays.toString(topic), e); - } - finally { - this.topicLock.unlock(); - } - } - - private void subscribe() { - this.topicLock.lock(); - String[] topics = getTopic(); - ApplicationEventPublisher applicationEventPublisher = getApplicationEventPublisher(); - try { - if (topics.length > 0) { - int[] requestedQos = getQos(); - IMqttMessageListener listener = this::messageArrived; - IMqttMessageListener[] listeners = Stream.of(topics) - .map(t -> listener) - .toArray(IMqttMessageListener[]::new); - IMqttToken subscribeToken = this.client.subscribe(topics, requestedQos, listeners); - subscribeToken.waitForCompletion(getCompletionTimeout()); - int[] grantedQos = subscribeToken.getGrantedQos(); - if (grantedQos.length == 1 && grantedQos[0] == 0x80) { // NOSONAR - throw new MqttException(MqttException.REASON_CODE_SUBSCRIBE_FAILED); - } - warnInvalidQosForSubscription(topics, requestedQos, grantedQos); - } - } - catch (MqttException ex) { - applicationEventPublisher.publishEvent(new MqttConnectionFailedEvent(this, ex)); - logger.error(ex, () -> "Error subscribing to " + Arrays.toString(topics)); - } - finally { - this.topicLock.unlock(); - } - if (this.client.isConnected()) { - String message = "Connected and subscribed to " + Arrays.toString(topics); - logger.debug(message); - applicationEventPublisher.publishEvent(new MqttSubscribedEvent(this, message)); - } - } - - private void warnInvalidQosForSubscription(String[] topics, int[] requestedQos, int[] grantedQos) { - for (int i = 0; i < requestedQos.length; i++) { - if (grantedQos[i] != requestedQos[i]) { - logger.warn(() -> "Granted QOS different to Requested QOS; topics: " + Arrays.toString(topics) - + " requested: " + Arrays.toString(requestedQos) - + " granted: " + Arrays.toString(grantedQos)); - break; - } - } - } - - @Override - public void connectionLost(Throwable cause) { - this.lock.lock(); - try { - if (isRunning()) { - this.logger.error(() -> "Lost connection: " + cause.getMessage()); - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, cause)); - } - else { - // The 'connectComplete()' re-subscribes or sets this flag otherwise. - this.readyToSubscribeOnStart = false; - } - } - finally { - this.lock.unlock(); - } - } - - @Override - public void messageArrived(String topic, MqttMessage mqttMessage) { - AbstractIntegrationMessageBuilder builder = toMessageBuilder(topic, mqttMessage); - if (builder != null) { - if (isManualAcks()) { - builder.setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, - new AcknowledgmentImpl(mqttMessage.getId(), mqttMessage.getQos(), this.client)); - } - Message message = builder.build(); - try { - sendMessage(message); - } - catch (RuntimeException ex) { - logger.error(ex, () -> "Unhandled exception for " + message); - throw ex; - } - } - } - - @SuppressWarnings("NullAway") // Dataflow analysis limitation - private AbstractIntegrationMessageBuilder toMessageBuilder(String topic, MqttMessage mqttMessage) { - AbstractIntegrationMessageBuilder builder = null; - Exception conversionError = null; - try { - builder = getConverter().toMessageBuilder(topic, mqttMessage); - } - catch (Exception ex) { - conversionError = ex; - } - - if (builder == null && conversionError == null) { - conversionError = new IllegalStateException("'MqttMessageConverter' returned 'null'"); - } - - if (conversionError != null) { - GenericMessage message = new GenericMessage<>(mqttMessage); - if (!sendErrorMessageIfNecessary(message, conversionError)) { - MessageConversionException conversionException; - if (conversionError instanceof MessageConversionException) { - conversionException = (MessageConversionException) conversionError; - } - else { - conversionException = new MessageConversionException(message, "Failed to convert from MQTT Message", - conversionError); - } - throw conversionException; - } - } - return builder; - } - - @Override - public void deliveryComplete(IMqttDeliveryToken token) { - } - - @Override - public void connectComplete(boolean isReconnect) { - connectComplete(isReconnect, getUrl()); - } - - @Override - public void connectComplete(boolean reconnect, @Nullable String serverURI) { - // The 'running' flag is set after 'doStart()', so possible a race condition - // when start is not finished yet, but server answers with successful connection. - if (isActive()) { - subscribe(); - } - else { - this.readyToSubscribeOnStart = true; - } - } - - /** - * Used to complete message arrival when {@link #isManualAcks()} is true. - * - * @since 5.3 - */ - private static class AcknowledgmentImpl implements SimpleAcknowledgment { - - private final int id; - - private final int qos; - - private final IMqttAsyncClient ackClient; - - /** - * Construct an instance with the provided properties. - * @param id the message id. - * @param qos the message QOS. - * @param client the client. - */ - AcknowledgmentImpl(int id, int qos, IMqttAsyncClient client) { - this.id = id; - this.qos = qos; - this.ackClient = client; - } - - @Override - public void acknowledge() { - if (this.ackClient != null) { - try { - this.ackClient.messageArrivedComplete(this.id, this.qos); - } - catch (MqttException e) { - throw new IllegalStateException(e); - } - } - else { - throw new IllegalStateException("Client has changed"); - } - } - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java deleted file mode 100644 index adc30b489e4..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.inbound; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.ConcurrentModificationException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.IntStream; - -import org.eclipse.paho.mqttv5.client.IMqttAsyncClient; -import org.eclipse.paho.mqttv5.client.IMqttToken; -import org.eclipse.paho.mqttv5.client.MqttAsyncClient; -import org.eclipse.paho.mqttv5.client.MqttCallback; -import org.eclipse.paho.mqttv5.client.MqttClientPersistence; -import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; -import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse; -import org.eclipse.paho.mqttv5.common.MqttException; -import org.eclipse.paho.mqttv5.common.MqttMessage; -import org.eclipse.paho.mqttv5.common.MqttSubscription; -import org.eclipse.paho.mqttv5.common.packet.MqttProperties; -import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.acks.SimpleAcknowledgment; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.integration.mqtt.core.ClientManager; -import org.springframework.integration.mqtt.core.MqttComponent; -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.event.MqttProtocolErrorEvent; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.support.MqttHeaderMapper; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.mqtt.support.MqttUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.converter.SmartMessageConverter; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; - -/** - * The {@link AbstractMqttMessageDrivenChannelAdapter} implementation for MQTT v5. - *

- * The {@link MqttProperties} are mapped via the provided {@link HeaderMapper}; - * meanwhile the regular {@link MqttMessage} properties are always mapped into headers. - *

- * It is recommended to have the {@link MqttConnectionOptions#setAutomaticReconnect(boolean)} - * set to true to let an internal {@link IMqttAsyncClient} instance to handle reconnects. - * Otherwise, only the manual restart of this component can handle reconnects, e.g. via - * {@link MqttConnectionFailedEvent} handling on disconnection. - *

- * See {@link #setPayloadType} for more information about type conversion. - * - * @author Artem Bilan - * @author Mikhail Polivakha - * @author Lucas Bowler - * @author Artem Vozhdayenko - * @author Matthias Thoma - * @author Glenn Renfro - * - * @since 5.5.5 - * - */ -public class Mqttv5PahoMessageDrivenChannelAdapter - extends AbstractMqttMessageDrivenChannelAdapter - implements MqttCallback, MqttComponent { - - private final Lock lock = new ReentrantLock(); - - private final MqttConnectionOptions connectionOptions; - - private @Nullable List subscriptions; - - @SuppressWarnings("NullAway.Init") - private IMqttAsyncClient mqttClient; - - @Nullable - private MqttClientPersistence persistence; - - @SuppressWarnings("NullAway.Init") - private SmartMessageConverter messageConverter; - - private Class payloadType = byte[].class; - - private HeaderMapper headerMapper = new MqttHeaderMapper(); - - private volatile boolean readyToSubscribeOnStart; - - /** - * Create an instance based on the MQTT url, client id and subscriptions. - * @param url the MQTT url to connect. - * @param clientId the unique client id. - * @param mqttSubscriptions the MQTT subscriptions. - * @since 6.3 - */ - public Mqttv5PahoMessageDrivenChannelAdapter(String url, String clientId, MqttSubscription... mqttSubscriptions) { - this(url, clientId, Arrays.stream(mqttSubscriptions).map(MqttSubscription::getTopic).toArray(String[]::new)); - this.subscriptions = new ArrayList<>(); - Collections.addAll(this.subscriptions, mqttSubscriptions); - } - - public Mqttv5PahoMessageDrivenChannelAdapter(String url, String clientId, String... topic) { - super(url, clientId, topic); - Assert.hasText(url, "'url' cannot be null or empty"); - this.connectionOptions = new MqttConnectionOptions(); - this.connectionOptions.setServerURIs(new String[] {url}); - this.connectionOptions.setAutomaticReconnect(true); - } - - /** - * Create an instance based on the MQTT connection options, client id and subscriptions. - * @param connectionOptions the MQTT connection options. - * @param clientId the unique client id. - * @param mqttSubscriptions the MQTT subscriptions. - * @since 6.3 - */ - public Mqttv5PahoMessageDrivenChannelAdapter(MqttConnectionOptions connectionOptions, String clientId, - MqttSubscription... mqttSubscriptions) { - - this(connectionOptions, clientId, - Arrays.stream(mqttSubscriptions).map(MqttSubscription::getTopic).toArray(String[]::new)); - this.subscriptions = new ArrayList<>(); - Collections.addAll(this.subscriptions, mqttSubscriptions); - } - - public Mqttv5PahoMessageDrivenChannelAdapter(MqttConnectionOptions connectionOptions, String clientId, - String... topic) { - - super(obtainServerUrlFromOptions(connectionOptions), clientId, topic); - this.connectionOptions = connectionOptions; - if (!this.connectionOptions.isAutomaticReconnect()) { - logger.warn("It is recommended to set 'automaticReconnect' MQTT client option. " + - "Otherwise the current channel adapter restart should be used explicitly, " + - "e.g. via handling 'MqttConnectionFailedEvent' on client disconnection."); - } - } - - /** - * Create an instance based on the client manager and subscriptions. - * @param clientManager The client manager. - * @param mqttSubscriptions the MQTT subscriptions. - * @since 6.3 - */ - public Mqttv5PahoMessageDrivenChannelAdapter(ClientManager clientManager, - MqttSubscription... mqttSubscriptions) { - - this(clientManager, Arrays.stream(mqttSubscriptions).map(MqttSubscription::getTopic).toArray(String[]::new)); - this.subscriptions = new ArrayList<>(); - Collections.addAll(this.subscriptions, mqttSubscriptions); - } - - /** - * Use this constructor when you need to use a single {@link ClientManager} - * (for instance, to reuse an MQTT connection). - * @param clientManager The client manager. - * @param topic The topic(s). - * @since 6.0 - */ - public Mqttv5PahoMessageDrivenChannelAdapter(ClientManager clientManager, - String... topic) { - - super(clientManager, topic); - this.connectionOptions = clientManager.getConnectionInfo(); - } - - @Override - public MqttConnectionOptions getConnectionInfo() { - return this.connectionOptions; - } - - public void setPersistence(@Nullable MqttClientPersistence persistence) { - this.persistence = persistence; - } - - @Override - public void setConverter(MqttMessageConverter converter) { - throw new UnsupportedOperationException("Use setMessageConverter(SmartMessageConverter) instead"); - } - - public void setMessageConverter(SmartMessageConverter messageConverter) { - this.messageConverter = messageConverter; - } - - /** - * Set the type of the target message payload to produce after conversion from MQTT message. - * Defaults to {@code byte[].class} - just extract MQTT message payload without conversion. - * Can be set to {@link MqttMessage} class to produce the whole MQTT message as a payload. - * @param payloadType the expected payload type to convert MQTT message to. - */ - public void setPayloadType(Class payloadType) { - Assert.notNull(payloadType, "'payloadType' must not be null."); - this.payloadType = payloadType; - } - - public void setHeaderMapper(HeaderMapper headerMapper) { - Assert.notNull(headerMapper, "'headerMapper' must not be null."); - this.headerMapper = headerMapper; - } - - @Override - protected void onInit() { - super.onInit(); - if (getClientManager() == null && this.mqttClient == null) { - try { - this.mqttClient = new MqttAsyncClient(getUrl(), getClientId(), this.persistence); - this.mqttClient.setCallback(this); - this.mqttClient.setManualAcks(isManualAcks()); - } - catch (MqttException ex) { - throw new BeanCreationException("Cannot create 'MqttAsyncClient' for: " + getComponentName(), ex); - } - } - if (this.messageConverter == null) { - setMessageConverter(getBeanFactory() - .getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, - SmartMessageConverter.class)); - } - } - - @Override - protected void doStart() { - try { - connect(); - if (this.readyToSubscribeOnStart) { - subscribe(); - } - } - catch (MqttException ex) { - if (getConnectionInfo().isAutomaticReconnect()) { - try { - this.mqttClient.reconnect(); - } - catch (MqttException re) { - logger.error(re, "MQTT client failed to connect. Never happens."); - } - } - else { - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, ex)); - logger.error(ex, "MQTT client failed to connect."); - } - } - } - - private void connect() throws MqttException { - this.lock.lock(); - try { - var clientManager = getClientManager(); - if (clientManager == null) { - this.mqttClient.connect(this.connectionOptions).waitForCompletion(getCompletionTimeout()); - } - else { - IMqttAsyncClient client = clientManager.getClient(); - Assert.state(client != null, "The 'client' must not be null, consider to start the 'clientManager'."); - this.mqttClient = client; - } - } - finally { - this.lock.unlock(); - } - } - - @Override - protected void doStop() { - this.topicLock.lock(); - this.readyToSubscribeOnStart = false; - String[] topics = getTopic(); - try { - if (this.mqttClient != null && this.mqttClient.isConnected()) { - if (this.connectionOptions.isCleanStart()) { - unsubscribe(topics); - // Have to re-subscribe on next start if connection is not lost. - this.readyToSubscribeOnStart = true; - - } - if (getClientManager() == null) { - this.mqttClient.disconnectForcibly(getQuiescentTimeout(), getDisconnectCompletionTimeout(), - MqttReturnCode.RETURN_CODE_SUCCESS, new MqttProperties()); - if (getConnectionInfo().isAutomaticReconnect()) { - MqttUtils.stopClientReconnectCycle(this.mqttClient); - } - } - } - } - catch (MqttException ex) { - logger.error(ex, () -> "Error unsubscribing from " + Arrays.toString(topics)); - } - finally { - this.topicLock.unlock(); - } - } - - private void unsubscribe(String... topics) throws MqttException { - try { - // Catch ConcurrentModificationException: https://github.com/eclipse/paho.mqtt.java/issues/986 - this.mqttClient.unsubscribe(topics).waitForCompletion(getCompletionTimeout()); - } - catch (ConcurrentModificationException ex) { - logger.error(ex, () -> "Error unsubscribing from " + Arrays.toString(topics)); - } - } - - @Override - public void destroy() { - super.destroy(); - try { - if (getClientManager() == null && this.mqttClient != null) { - this.mqttClient.close(); - } - } - catch (MqttException ex) { - logger.error(ex, "Failed to close 'MqttAsyncClient'"); - } - } - - @Override - public void setQos(int... qos) { - Assert.isNull(this.subscriptions, "The 'qos' must be provided with the 'MqttSubscription'."); - super.setQos(qos); - } - - @Override - public void addTopic(String topic, int qos) { - this.topicLock.lock(); - try { - super.addTopic(topic, qos); - MqttSubscription subscription = new MqttSubscription(topic, qos); - if (this.subscriptions != null) { - this.subscriptions.add(subscription); - } - if (this.mqttClient != null && this.mqttClient.isConnected()) { - MqttProperties subscriptionProperties = new MqttProperties(); - // Make use of mqttSession.getNextSubscriptionIdentifier() if available in connection - subscriptionProperties.setSubscriptionIdentifiers(List.of(0)); - this.mqttClient.subscribe(new MqttSubscription[] {subscription}, - null, null, this::messageArrived, subscriptionProperties) - .waitForCompletion(getCompletionTimeout()); - } - } - catch (MqttException ex) { - throw new MessagingException("Failed to subscribe to topic " + topic, ex); - } - finally { - this.topicLock.unlock(); - } - } - - @Override - public void removeTopic(String... topic) { - this.topicLock.lock(); - try { - if (this.mqttClient != null && this.mqttClient.isConnected()) { - unsubscribe(topic); - } - super.removeTopic(topic); - if (!CollectionUtils.isEmpty(this.subscriptions)) { - this.subscriptions.removeIf((sub) -> ObjectUtils.containsElement(topic, sub.getTopic())); - } - } - catch (MqttException ex) { - throw new MessagingException("Failed to unsubscribe from topic(s) " + Arrays.toString(topic), ex); - } - finally { - this.topicLock.unlock(); - } - } - - @Override - public void messageArrived(String topic, MqttMessage mqttMessage) { - Map headers = this.headerMapper.toHeaders(mqttMessage.getProperties()); - headers.put(MqttHeaders.ID, mqttMessage.getId()); - headers.put(MqttHeaders.RECEIVED_QOS, mqttMessage.getQos()); - headers.put(MqttHeaders.DUPLICATE, mqttMessage.isDuplicate()); - headers.put(MqttHeaders.RECEIVED_RETAINED, mqttMessage.isRetained()); - headers.put(MqttHeaders.RECEIVED_TOPIC, topic); - - if (isManualAcks()) { - headers.put(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, - new AcknowledgmentImpl(mqttMessage.getId(), mqttMessage.getQos(), this.mqttClient)); - } - - Object payload = - MqttMessage.class.isAssignableFrom(this.payloadType) - ? mqttMessage - : mqttMessage.getPayload(); - - Message message; - if (MqttMessage.class.isAssignableFrom(this.payloadType) || byte[].class.isAssignableFrom(this.payloadType)) { - message = new GenericMessage<>(payload, headers); - } - else { - message = this.messageConverter.toMessage(payload, new MessageHeaders(headers), this.payloadType); - } - - try { - sendMessage(message); - } - catch (RuntimeException ex) { - logger.error(ex, () -> "Unhandled exception for " + message); - throw ex; - } - } - - @Override - public void disconnected(MqttDisconnectResponse disconnectResponse) { - if (isRunning()) { - MqttException cause = disconnectResponse.getException(); - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, cause)); - } - else { - // The 'connectComplete()' re-subscribes or sets this flag otherwise. - this.readyToSubscribeOnStart = false; - } - } - - @Override - public void mqttErrorOccurred(MqttException exception) { - getApplicationEventPublisher().publishEvent(new MqttProtocolErrorEvent(this, exception)); - } - - @Override - public void deliveryComplete(IMqttToken token) { - - } - - @Override - public void connectComplete(boolean isReconnect) { - connectComplete(isReconnect, getUrl()); - } - - @Override - public void connectComplete(boolean reconnect, @Nullable String serverURI) { - // The 'running' flag is set after 'doStart()', so possible a race condition - // when start is not finished yet, but server answers with successful connection. - if (isActive()) { - subscribe(); - } - else { - this.readyToSubscribeOnStart = true; - } - } - - private void subscribe() { - var clientManager = getClientManager(); - if (clientManager != null && this.mqttClient == null) { - IMqttAsyncClient client = clientManager.getClient(); - Assert.state(client != null, "The 'client' must not be null, consider to start the 'clientManager'."); - this.mqttClient = client; - } - - MqttSubscription[] mqttSubscriptions = obtainSubscriptions(); - if (ObjectUtils.isEmpty(mqttSubscriptions)) { - return; - } - ApplicationEventPublisher applicationEventPublisher = getApplicationEventPublisher(); - this.topicLock.lock(); - try { - MqttProperties subscriptionProperties = new MqttProperties(); - // Make use of mqttSession.getNextSubscriptionIdentifier() if available in connection - subscriptionProperties.setSubscriptionIdentifiers(List.of(0)); - this.mqttClient.subscribe(mqttSubscriptions, null, null, this::messageArrived, subscriptionProperties) - .waitForCompletion(getCompletionTimeout()); - String message = "Connected and subscribed to " + Arrays.toString(mqttSubscriptions); - logger.debug(message); - applicationEventPublisher.publishEvent(new MqttSubscribedEvent(this, message)); - } - catch (MqttException ex) { - applicationEventPublisher.publishEvent(new MqttConnectionFailedEvent(this, ex)); - logger.error(ex, () -> "Error subscribing to " + Arrays.toString(mqttSubscriptions)); - } - finally { - this.topicLock.unlock(); - } - } - - private MqttSubscription @Nullable [] obtainSubscriptions() { - if (this.subscriptions != null) { - return this.subscriptions.toArray(new MqttSubscription[0]); - } - else { - String[] topics = getTopic(); - if (topics.length == 0) { - return null; - } - int[] requestedQos = getQos(); - return IntStream.range(0, topics.length) - .mapToObj(i -> new MqttSubscription(topics[i], requestedQos[i])) - .toArray(MqttSubscription[]::new); - } - } - - @Override - public void authPacketArrived(int reasonCode, MqttProperties properties) { - - } - - private static String obtainServerUrlFromOptions(MqttConnectionOptions connectionOptions) { - Assert.notNull(connectionOptions, "'connectionOptions' must not be null"); - String[] serverURIs = connectionOptions.getServerURIs(); - Assert.notEmpty(serverURIs, "'serverURIs' must be provided in the 'MqttConnectionOptions'"); - return serverURIs[0]; - } - - /** - * Used to complete message arrival when {@link #isManualAcks()} is true. - */ - private static class AcknowledgmentImpl implements SimpleAcknowledgment { - - private final int id; - - private final int qos; - - private final IMqttAsyncClient ackClient; - - /** - * Construct an instance with the provided properties. - * @param id the message id. - * @param qos the message QOS. - * @param client the client. - */ - AcknowledgmentImpl(int id, int qos, IMqttAsyncClient client) { - this.id = id; - this.qos = qos; - this.ackClient = client; - } - - @Override - public void acknowledge() { - try { - this.ackClient.messageArrivedComplete(this.id, this.qos); - } - catch (MqttException ex) { - throw new IllegalStateException(ex); - } - } - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/package-info.java deleted file mode 100644 index ebf9acdee1a..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides inbound Spring Integration MqttAdapter components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.inbound; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java deleted file mode 100644 index f87e6ce8d6a..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.outbound; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.expression.Expression; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.mqtt.core.ClientManager; -import org.springframework.integration.mqtt.event.MqttMessageDeliveredEvent; -import org.springframework.integration.mqtt.event.MqttMessageNotDeliveredEvent; -import org.springframework.integration.mqtt.event.MqttMessageSentEvent; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.support.management.ManageableLifecycle; -import org.springframework.messaging.Message; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.util.Assert; - -/** - * Abstract class for MQTT outbound channel adapters. - * - * @param MQTT Client type - * @param MQTT connection options type (v5 or v3) - * - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 4.0 - * - */ -public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler - implements ManageableLifecycle, ApplicationEventPublisherAware { - - /** - * The default disconnect completion timeout in milliseconds. - */ - public static final long DISCONNECT_COMPLETION_TIMEOUT = 5_000L; - - /** - * The default completion timeout in milliseconds. - */ - public static final long DEFAULT_COMPLETION_TIMEOUT = 30_000L; - - private static final MessageProcessor DEFAULT_TOPIC_PROCESSOR = - (message) -> message.getHeaders().get(MqttHeaders.TOPIC, String.class); - - protected final Lock lock = new ReentrantLock(); - - private final AtomicBoolean running = new AtomicBoolean(); - - private final @Nullable String url; - - private final String clientId; - - private final @Nullable ClientManager clientManager; - - private boolean async; - - private boolean asyncEvents; - - private long completionTimeout = DEFAULT_COMPLETION_TIMEOUT; - - private long disconnectCompletionTimeout = DISCONNECT_COMPLETION_TIMEOUT; - - private @Nullable String defaultTopic; - - private MessageProcessor topicProcessor = DEFAULT_TOPIC_PROCESSOR; - - private int defaultQos = 0; - - private MessageProcessor qosProcessor = MqttMessageConverter.defaultQosProcessor(); - - private boolean defaultRetained; - - private MessageProcessor retainedProcessor = MqttMessageConverter.defaultRetainedProcessor(); - - @SuppressWarnings("NullAway.Init") - private MessageConverter converter; - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - private int clientInstance; - - public AbstractMqttMessageHandler(@Nullable String url, String clientId) { - Assert.hasText(clientId, "'clientId' cannot be null or empty"); - this.url = url; - this.clientId = clientId; - this.clientManager = null; - } - - public AbstractMqttMessageHandler(ClientManager clientManager) { - Assert.notNull(clientManager, "'clientManager' cannot be null or empty"); - this.clientManager = clientManager; - this.url = clientManager.getUrl(); - this.clientId = clientManager.getClientId(); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - protected ApplicationEventPublisher getApplicationEventPublisher() { - return this.applicationEventPublisher; - } - - /** - * Set the topic to which the message will be published if the - * {@link #setTopicExpression(Expression) topicExpression} evaluates to `null`. - * @param defaultTopic the default topic. - */ - public void setDefaultTopic(String defaultTopic) { - Assert.hasText(defaultTopic, "'defaultTopic' must not be empty"); - this.defaultTopic = defaultTopic; - } - - protected @Nullable String getDefaultTopic() { - return this.defaultTopic; - } - - /** - * Set the topic expression; default "headers['mqtt_topic']". - * @param topicExpression the expression. - * @since 5.0 - */ - public void setTopicExpression(Expression topicExpression) { - Assert.notNull(topicExpression, "'topicExpression' cannot be null"); - this.topicProcessor = new ExpressionEvaluatingMessageProcessor<>(topicExpression); - } - - /** - * Set the topic expression; default "headers['mqtt_topic']". - * @param topicExpression the expression. - * @since 5.0 - */ - public void setTopicExpressionString(String topicExpression) { - Assert.hasText(topicExpression, "'topicExpression' must not be null or empty"); - this.topicProcessor = new ExpressionEvaluatingMessageProcessor<>(topicExpression); - } - - protected MessageProcessor getTopicProcessor() { - return this.topicProcessor; - } - - /** - * Set the qos for messages if the {@link #setQosExpression(Expression) qosExpression} - * evaluates to null. Only applies if a message converter is not provided. - * @param defaultQos the default qos. - * @see #setConverter(MessageConverter) - */ - public void setDefaultQos(int defaultQos) { - this.defaultQos = defaultQos; - } - - protected int getDefaultQos() { - return this.defaultQos; - } - - /** - * Set the qos expression; default "headers['mqtt_qos']". - * Only applies if a message converter is not provided. - * @param qosExpression the expression. - * @since 5.0 - * @see #setConverter(MessageConverter) - */ - public void setQosExpression(Expression qosExpression) { - Assert.notNull(qosExpression, "'qosExpression' cannot be null"); - this.qosProcessor = new ExpressionEvaluatingMessageProcessor<>(qosExpression); - } - - /** - * Set the qos expression; default "headers['mqtt_qos']". - * Only applies if a message converter is not provided. - * @param qosExpression the expression. - * @since 5.0 - * @see #setConverter(MessageConverter) - */ - public void setQosExpressionString(String qosExpression) { - Assert.hasText(qosExpression, "'qosExpression' must not be null or empty"); - this.qosProcessor = new ExpressionEvaluatingMessageProcessor<>(qosExpression); - } - - protected MessageProcessor getQosProcessor() { - return this.qosProcessor; - } - - /** - * Set the retained boolean for messages if the - * {@link #setRetainedExpression(Expression) retainedExpression} evaluates to null. - * Only applies if a message converter is not provided. - * @param defaultRetained the default defaultRetained. - * @see #setConverter(MessageConverter) - */ - public void setDefaultRetained(boolean defaultRetained) { - this.defaultRetained = defaultRetained; - } - - protected boolean getDefaultRetained() { - return this.defaultRetained; - } - - /** - * Set the retained expression; default "headers['mqtt_retained']". - * Only applies if a message converter is not provided. - * @param retainedExpression the expression. - * @since 5.0 - * @see #setConverter(MessageConverter) - */ - public void setRetainedExpression(Expression retainedExpression) { - Assert.notNull(retainedExpression, "'qosExpression' cannot be null"); - this.retainedProcessor = new ExpressionEvaluatingMessageProcessor<>(retainedExpression); - } - - /** - * Set the retained expression; default "headers['mqtt_retained']". - * Only applies if a message converter is not provided. - * @param retainedExpression the expression. - * @since 5.0 - * @see #setConverter(MessageConverter) - */ - public void setRetainedExpressionString(String retainedExpression) { - Assert.hasText(retainedExpression, "'qosExpression' must not be null or empty"); - this.retainedProcessor = new ExpressionEvaluatingMessageProcessor<>(retainedExpression); - } - - protected MessageProcessor getRetainedProcessor() { - return this.retainedProcessor; - } - - /** - * Set the message converter to use; if this is provided, the adapter qos and retained - * settings are ignored. - * @param converter the converter. - */ - public void setConverter(MessageConverter converter) { - Assert.notNull(converter, "'converter' cannot be null"); - this.converter = converter; - } - - protected MessageConverter getConverter() { - return this.converter; - } - - @Nullable - protected String getUrl() { - return this.url; - } - - public String getClientId() { - return this.clientId; - } - - /** - * Incremented each time the client is connected. - * @return The instance; - * @since 4.1 - */ - public int getClientInstance() { - return this.clientInstance; - } - - @Override - public String getComponentType() { - return "mqtt:outbound-channel-adapter"; - } - - protected void incrementClientInstance() { - this.clientInstance++; - } - - /** - * Set the completion timeout for async operations. Not settable using the namespace. - * Default {@value #DEFAULT_COMPLETION_TIMEOUT} milliseconds. - * @param completionTimeout The timeout. - * @since 4.1 - */ - public void setCompletionTimeout(long completionTimeout) { - this.completionTimeout = completionTimeout; - } - - protected long getCompletionTimeout() { - return this.completionTimeout; - } - - /** - * Set the completion timeout when disconnecting. Not settable using the namespace. - * Default {@value #DISCONNECT_COMPLETION_TIMEOUT} milliseconds. - * @param completionTimeout The timeout. - * @since 5.1.10 - */ - public void setDisconnectCompletionTimeout(long completionTimeout) { - this.disconnectCompletionTimeout = completionTimeout; - } - - protected long getDisconnectCompletionTimeout() { - return this.disconnectCompletionTimeout; - } - - @Nullable - protected ClientManager getClientManager() { - return this.clientManager; - } - - /** - * Set to true if you don't want to block when sending messages. Default false. - * When true, message sent/delivered events will be published for reception - * by a suitably configured 'ApplicationListener' or an event - * inbound-channel-adapter. - * @param async true for async. - * @see #setAsyncEvents(boolean) - */ - public void setAsync(boolean async) { - this.async = async; - } - - protected boolean isAsync() { - return this.async; - } - - /** - * When {@link #setAsync(boolean)} is true, setting this to true enables - * publication of {@link MqttMessageSentEvent} and {@link MqttMessageDeliveredEvent} - * to be emitted. Default false. - * @param asyncEvents the asyncEvents. - */ - public void setAsyncEvents(boolean asyncEvents) { - this.asyncEvents = asyncEvents; - } - - @Override - protected void onInit() { - super.onInit(); - BeanFactory beanFactory = getBeanFactory(); - if (this.topicProcessor instanceof BeanFactoryAware beanFactoryAware) { - beanFactoryAware.setBeanFactory(beanFactory); - } - if (this.qosProcessor instanceof BeanFactoryAware beanFactoryAware) { - beanFactoryAware.setBeanFactory(beanFactory); - } - if (this.retainedProcessor instanceof BeanFactoryAware beanFactoryAware) { - beanFactoryAware.setBeanFactory(beanFactory); - } - } - - @Override - public final void start() { - if (!this.running.getAndSet(true)) { - doStart(); - } - } - - protected abstract void doStart(); - - @Override - public final void stop() { - if (this.running.getAndSet(false)) { - doStop(); - } - } - - protected abstract void doStop(); - - @Override - public boolean isRunning() { - return this.running.get(); - } - - @Override - protected void handleMessageInternal(Message message) { - Object mqttMessage = this.converter.fromMessage(message, Object.class); - Assert.state(mqttMessage != null, - () -> "The MQTT payload cannot be null. The '" + this.converter + "' returned null for: " + message); - - String topic = this.topicProcessor.processMessage(message); - if (topic == null) { - topic = this.defaultTopic; - } - Assert.state(topic != null, "No topic could be determined from the message and no default topic defined"); - - publish(topic, mqttMessage, message); - } - - protected void messageSentEvent(Message message, String topic, int messageId) { - if (this.async && this.asyncEvents) { - getApplicationEventPublisher().publishEvent( - new MqttMessageSentEvent(this, message, topic, messageId, getClientId(), - getClientInstance())); - } - } - - protected void sendDeliveryCompleteEvent(int messageId) { - if (this.async && this.asyncEvents) { - getApplicationEventPublisher().publishEvent( - new MqttMessageDeliveredEvent(this, messageId, getClientId(), getClientInstance())); - } - } - - protected void sendFailedDeliveryEvent(int messageId, Throwable exception) { - if (this.async && this.asyncEvents) { - getApplicationEventPublisher().publishEvent( - new MqttMessageNotDeliveredEvent(this, messageId, getClientId(), getClientInstance(), exception)); - } - } - - protected abstract void publish(String topic, Object mqttMessage, Message message); - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/MqttPahoMessageHandler.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/MqttPahoMessageHandler.java deleted file mode 100644 index 5f54b643b2f..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/MqttPahoMessageHandler.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.outbound; - -import org.eclipse.paho.client.mqttv3.IMqttActionListener; -import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.IMqttToken; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.mqtt.core.ClientManager; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.core.MqttPahoClientFactory; -import org.springframework.integration.mqtt.core.MqttPahoComponent; -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.mqtt.support.MqttUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.util.Assert; - -/** - * Eclipse Paho Implementation. When consuming {@link org.springframework.integration.mqtt.event.MqttIntegrationEvent}s - * published by this component use {@code MqttPahoComponent handler = event.getSourceAsType()} to get a - * reference, allowing you to obtain the bean name and {@link MqttConnectOptions}. This - * technique allows consumption of events from both inbound and outbound endpoints in the - * same event listener. - * - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * @author Christian Tzolov - * - * @since 4.0 - * - */ -public class MqttPahoMessageHandler extends AbstractMqttMessageHandler - implements MqttCallback, MqttPahoComponent { - - private final MqttPahoClientFactory clientFactory; - - private final IMqttActionListener mqttPublishActionListener = new MqttPublishActionListener(); - - private volatile @Nullable IMqttAsyncClient client; - - /** - * Use this constructor when you don't need additional {@link MqttConnectOptions}. - * @param url The URL. - * @param clientId The client id. - */ - public MqttPahoMessageHandler(String url, String clientId) { - this(url, clientId, new DefaultMqttPahoClientFactory()); - } - - /** - * Use this constructor for a single url (although it may be overridden if the server - * URI(s) are provided by the {@link MqttConnectOptions#getServerURIs()} provided by - * the {@link MqttPahoClientFactory}). - * @param url the URL. - * @param clientId The client id. - * @param clientFactory The client factory. - */ - public MqttPahoMessageHandler(String url, String clientId, MqttPahoClientFactory clientFactory) { - super(url, clientId); - this.clientFactory = clientFactory; - } - - /** - * Use this constructor if the server URI(s) are provided by the {@link MqttConnectOptions#getServerURIs()} - * provided by the {@link MqttPahoClientFactory}. - * @param clientId The client id. - * @param clientFactory The client factory. - * @since 4.1 - */ - public MqttPahoMessageHandler(String clientId, MqttPahoClientFactory clientFactory) { - super(null, clientId); - this.clientFactory = clientFactory; - } - - /** - * Use this constructor when you need to use a single {@link ClientManager} - * (for instance, to reuse an MQTT connection). - * @param clientManager The client manager. - * @since 6.0 - */ - public MqttPahoMessageHandler(ClientManager clientManager) { - super(clientManager); - var factory = new DefaultMqttPahoClientFactory(); - factory.setConnectionOptions(clientManager.getConnectionInfo()); - this.clientFactory = factory; - } - - @Override - public MqttConnectOptions getConnectionInfo() { - MqttConnectOptions options = this.clientFactory.getConnectionOptions(); - if (options.getServerURIs() == null) { - String url = getUrl(); - if (url != null) { - options = MqttUtils.cloneConnectOptions(options); - options.setServerURIs(new String[] {url}); - } - } - return options; - } - - @Override - protected void onInit() { - super.onInit(); - MessageConverter converter = getConverter(); - if (converter == null) { - DefaultPahoMessageConverter defaultConverter = new DefaultPahoMessageConverter(getDefaultQos(), - getQosProcessor(), getDefaultRetained(), getRetainedProcessor()); - defaultConverter.setBeanFactory(getBeanFactory()); - setConverter(defaultConverter); - } - else { - Assert.state(converter instanceof MqttMessageConverter, "MessageConverter must be an MqttMessageConverter"); - } - } - - @Override - protected void doStart() { - } - - @Override - protected void doStop() { - try { - IMqttAsyncClient theClient = this.client; - if (theClient != null) { - theClient.disconnect().waitForCompletion(getDisconnectCompletionTimeout()); - if (getConnectionInfo().isAutomaticReconnect()) { - MqttUtils.stopClientReconnectCycle(theClient); - } - theClient.close(); - this.client = null; - } - } - catch (MqttException ex) { - logger.error(ex, "Failed to disconnect"); - } - } - - private IMqttAsyncClient checkConnection() throws MqttException { - this.lock.lock(); - try { - var theClientManager = getClientManager(); - if (theClientManager != null) { - IMqttAsyncClient theClient = theClientManager.getClient(); - Assert.state(theClient != null, "The 'client' must not be null, consider to start the 'clientManager'."); - return theClient; - } - - if (this.client != null && !this.client.isConnected()) { - this.client.setCallback(null); - this.client.close(); - this.client = null; - } - if (this.client == null) { - try { - MqttConnectOptions connectionOptions = this.clientFactory.getConnectionOptions(); - Assert.state(this.getUrl() != null || connectionOptions.getServerURIs() != null, - "If no 'url' provided, connectionOptions.getServerURIs() must not be null"); - this.client = this.clientFactory.getAsyncClientInstance(this.getUrl(), this.getClientId()); - incrementClientInstance(); - this.client.setCallback(this); - this.client.connect(connectionOptions).waitForCompletion(getCompletionTimeout()); - logger.debug("Client connected"); - } - catch (MqttException e) { - if (this.client != null) { - this.client.close(); - this.client = null; - } - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, e)); - throw new MessagingException("Failed to connect", e); - } - } - return this.client; - } - finally { - this.lock.unlock(); - } - } - - @Override - protected void publish(String topic, Object mqttMessage, Message message) { - Assert.isInstanceOf(MqttMessage.class, mqttMessage, "The 'mqttMessage' must be an instance of 'MqttMessage'"); - try { - IMqttDeliveryToken token = checkConnection() - .publish(topic, (MqttMessage) mqttMessage, null, this.mqttPublishActionListener); - if (!isAsync()) { - token.waitForCompletion(getCompletionTimeout()); // NOSONAR (sync) - } - else { - messageSentEvent(message, topic, token.getMessageId()); - } - } - catch (MqttException e) { - throw new MessageHandlingException(message, "Failed to publish to MQTT in the [" + this + ']', e); - } - } - - @Override - public void connectionLost(Throwable cause) { - this.lock.lock(); - try { - logger.error("Lost connection; will attempt reconnect on next request"); - if (this.client != null) { - try { - this.client.setCallback(null); - this.client.close(); - } - catch (MqttException e) { - // NOSONAR - } - this.client = null; - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, cause)); - } - } - finally { - this.lock.unlock(); - } - } - - @Override - public void messageArrived(String topic, MqttMessage message) { - - } - - @Override - public void deliveryComplete(IMqttDeliveryToken token) { - - } - - private final class MqttPublishActionListener implements IMqttActionListener { - - MqttPublishActionListener() { - } - - @Override - public void onSuccess(IMqttToken asyncActionToken) { - sendDeliveryCompleteEvent(asyncActionToken.getMessageId()); - } - - @Override - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - sendFailedDeliveryEvent(asyncActionToken.getMessageId(), exception); - } - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/Mqttv5PahoMessageHandler.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/Mqttv5PahoMessageHandler.java deleted file mode 100644 index 638bae526e4..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/Mqttv5PahoMessageHandler.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.outbound; - -import java.nio.charset.StandardCharsets; - -import org.eclipse.paho.mqttv5.client.IMqttAsyncClient; -import org.eclipse.paho.mqttv5.client.IMqttToken; -import org.eclipse.paho.mqttv5.client.MqttActionListener; -import org.eclipse.paho.mqttv5.client.MqttAsyncClient; -import org.eclipse.paho.mqttv5.client.MqttCallback; -import org.eclipse.paho.mqttv5.client.MqttClientPersistence; -import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; -import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse; -import org.eclipse.paho.mqttv5.common.MqttException; -import org.eclipse.paho.mqttv5.common.MqttMessage; -import org.eclipse.paho.mqttv5.common.packet.MqttProperties; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.integration.mqtt.core.ClientManager; -import org.springframework.integration.mqtt.core.MqttComponent; -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.event.MqttProtocolErrorEvent; -import org.springframework.integration.mqtt.support.MqttHeaderMapper; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.mqtt.support.MqttUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.util.Assert; - -/** - * The {@link AbstractMqttMessageHandler} implementation for MQTT v5. - * - * @author Artem Bilan - * @author Lucas Bowler - * @author Artem Vozhdayenko - * - * @since 5.5.5 - */ -public class Mqttv5PahoMessageHandler extends AbstractMqttMessageHandler - implements MqttCallback, MqttComponent { - - private final MqttConnectionOptions connectionOptions; - - private final MqttActionListener mqttPublishActionListener = new MqttPublishActionListener(); - - @SuppressWarnings("NullAway.Init") - private IMqttAsyncClient mqttClient; - - @Nullable - private MqttClientPersistence persistence; - - private HeaderMapper headerMapper = new MqttHeaderMapper(); - - public Mqttv5PahoMessageHandler(String url, String clientId) { - super(url, clientId); - Assert.hasText(url, "'url' cannot be null or empty"); - this.connectionOptions = new MqttConnectionOptions(); - this.connectionOptions.setServerURIs(new String[] {url}); - this.connectionOptions.setAutomaticReconnect(true); - } - - public Mqttv5PahoMessageHandler(MqttConnectionOptions connectionOptions, String clientId) { - super(obtainServerUrlFromOptions(connectionOptions), clientId); - this.connectionOptions = connectionOptions; - } - - /** - * Use this constructor when you need to use a single {@link ClientManager} - * (for instance, to reuse an MQTT connection). - * @param clientManager The client manager. - * @since 6.0 - */ - public Mqttv5PahoMessageHandler(ClientManager clientManager) { - super(clientManager); - this.connectionOptions = clientManager.getConnectionInfo(); - } - - private static String obtainServerUrlFromOptions(MqttConnectionOptions connectionOptions) { - Assert.notNull(connectionOptions, "'connectionOptions' must not be null"); - String[] serverURIs = connectionOptions.getServerURIs(); - Assert.notEmpty(serverURIs, "'serverURIs' must be provided in the 'MqttConnectionOptions'"); - return serverURIs[0]; - } - - @Override - public MqttConnectionOptions getConnectionInfo() { - return this.connectionOptions; - } - - public void setPersistence(@Nullable MqttClientPersistence persistence) { - this.persistence = persistence; - } - - public void setHeaderMapper(HeaderMapper headerMapper) { - Assert.notNull(headerMapper, "'headerMapper' must not be null"); - this.headerMapper = headerMapper; - } - - @Override - protected void onInit() { - super.onInit(); - try { - if (getClientManager() == null) { - this.mqttClient = new MqttAsyncClient(getUrl(), getClientId(), this.persistence); - this.mqttClient.setCallback(this); - incrementClientInstance(); - } - } - catch (MqttException ex) { - throw new BeanCreationException("Cannot create 'MqttAsyncClient' for: " + getComponentName(), ex); - } - if (getConverter() == null) { - setConverter(getBeanFactory() - .getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, - MessageConverter.class)); - } - else { - Assert.state(!(getConverter() instanceof MqttMessageConverter), - "MessageConverter must not be an MqttMessageConverter"); - } - } - - @Override - protected void doStart() { - try { - var clientManager = getClientManager(); - if (clientManager != null) { - IMqttAsyncClient client = clientManager.getClient(); - Assert.state(client != null, "The 'client' must not be null, consider to start the 'clientManager'."); - this.mqttClient = client; - } - else { - this.mqttClient.connect(this.connectionOptions).waitForCompletion(getCompletionTimeout()); - } - } - catch (MqttException ex) { - logger.error(ex, "MQTT client failed to connect."); - } - } - - @Override - protected void doStop() { - try { - if (getClientManager() == null) { - this.mqttClient.disconnect().waitForCompletion(getDisconnectCompletionTimeout()); - if (getConnectionInfo().isAutomaticReconnect()) { - MqttUtils.stopClientReconnectCycle(this.mqttClient); - } - } - } - catch (MqttException ex) { - logger.error(ex, "Failed to disconnect 'MqttAsyncClient'"); - } - } - - @Override - public void destroy() { - super.destroy(); - try { - if (getClientManager() == null) { - this.mqttClient.close(true); - } - } - catch (MqttException ex) { - logger.error(ex, "Failed to close 'MqttAsyncClient'"); - } - } - - @Override - protected void handleMessageInternal(Message message) { - MqttMessage mqttMessage; - Object payload = message.getPayload(); - if (payload instanceof MqttMessage) { - mqttMessage = (MqttMessage) payload; - } - else { - mqttMessage = buildMqttMessage(message); - } - - publish(obtainTopicToPublish(message), mqttMessage, message); - } - - private String obtainTopicToPublish(Message message) { - String topic = getTopicProcessor().processMessage(message); - if (topic == null) { - topic = getDefaultTopic(); - } - Assert.state(topic != null, - () -> "No topic could be determined from the '" + message + "' and no default topic defined"); - return topic; - } - - private MqttMessage buildMqttMessage(Message message) { - Object payload = message.getPayload(); - byte[] body; - if (payload instanceof byte[]) { - body = (byte[]) payload; - } - else if (payload instanceof String) { - body = ((String) payload).getBytes(StandardCharsets.UTF_8); - } - else { - MessageConverter converter = getConverter(); - body = (byte[]) converter.fromMessage(message, byte[].class); - Assert.state(body != null, - () -> "The MQTT payload cannot be null. The '" + converter + "' returned null for: " + message); - } - - MqttMessage mqttMessage = new MqttMessage(); - mqttMessage.setPayload(body); - Integer qos = getQosProcessor().processMessage(message); - mqttMessage.setQos(qos == null ? getDefaultQos() : qos); - Boolean retained = getRetainedProcessor().processMessage(message); - mqttMessage.setRetained(retained == null ? getDefaultRetained() : retained); - MqttProperties properties = new MqttProperties(); - this.headerMapper.fromHeaders(message.getHeaders(), properties); - mqttMessage.setProperties(properties); - return mqttMessage; - } - - @Override - protected void publish(String topic, Object mqttMessage, Message message) { - Assert.isInstanceOf(MqttMessage.class, mqttMessage, "The 'mqttMessage' must be an instance of 'MqttMessage'"); - long completionTimeout = getCompletionTimeout(); - try { - if (!this.mqttClient.isConnected()) { - this.lock.lock(); - try { - if (!this.mqttClient.isConnected()) { - this.mqttClient.connect(this.connectionOptions).waitForCompletion(completionTimeout); - } - } - finally { - this.lock.unlock(); - } - } - IMqttToken token = - this.mqttClient.publish(topic, (MqttMessage) mqttMessage, null, this.mqttPublishActionListener); - if (!isAsync()) { - token.waitForCompletion(completionTimeout); // NOSONAR (sync) - } - else { - messageSentEvent(message, topic, token.getMessageId()); - } - } - catch (MqttException ex) { - throw new MessageHandlingException(message, "Failed to publish to MQTT in the [" + this + ']', ex); - } - } - - @Override - public void deliveryComplete(IMqttToken token) { - - } - - @Override - public void disconnected(MqttDisconnectResponse disconnectResponse) { - MqttException cause = disconnectResponse.getException(); - getApplicationEventPublisher().publishEvent(new MqttConnectionFailedEvent(this, cause)); - } - - @Override - public void mqttErrorOccurred(MqttException exception) { - getApplicationEventPublisher().publishEvent(new MqttProtocolErrorEvent(this, exception)); - } - - @Override - public void messageArrived(String topic, MqttMessage message) { - - } - - @Override - public void connectComplete(boolean reconnect, String serverURI) { - - } - - @Override - public void authPacketArrived(int reasonCode, MqttProperties properties) { - - } - - private final class MqttPublishActionListener implements MqttActionListener { - - MqttPublishActionListener() { - } - - @Override - public void onSuccess(IMqttToken asyncActionToken) { - sendDeliveryCompleteEvent(asyncActionToken.getMessageId()); - } - - @Override - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - sendFailedDeliveryEvent(asyncActionToken.getMessageId(), exception); - } - - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/package-info.java deleted file mode 100644 index 435d158c348..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides Spring Integration components for doing outbound operations. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.outbound; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/package-info.java deleted file mode 100644 index 67d659a308e..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Root package of the MqttAdapter Module. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt; diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/DefaultPahoMessageConverter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/DefaultPahoMessageConverter.java deleted file mode 100644 index ad01fc36d86..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/DefaultPahoMessageConverter.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.support; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.mapping.BytesMessageMapper; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.DefaultMessageBuilderFactory; -import org.springframework.integration.support.MessageBuilderFactory; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.converter.MessageConversionException; -import org.springframework.util.Assert; - -/** - * Default implementation for mapping to/from Messages. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -public class DefaultPahoMessageConverter implements MqttMessageConverter, BeanFactoryAware { - - private final Charset charset; - - private final int defaultQos; - - private final MessageProcessor qosProcessor; - - private final boolean defaultRetained; - - private final MessageProcessor retainedProcessor; - - private @Nullable BytesMessageMapper bytesMessageMapper; - - private boolean payloadAsBytes = false; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - private volatile MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - private volatile boolean messageBuilderFactorySet; - - /** - * Construct a converter with default options (qos=0, retain=false, charset=UTF-8). - */ - public DefaultPahoMessageConverter() { - this(0, false); - } - - /** - * Construct a converter to create outbound messages with the supplied default qos and - * retain settings and a UTF-8 charset for converting outbound String payloads to - * {@code byte[]} and inbound {@code byte[]} to String (unless - * {@link #setPayloadAsBytes(boolean) payloadAdBytes} is true). - * @param defaultQos the default qos. - * @param defaultRetained the default retained. - */ - public DefaultPahoMessageConverter(int defaultQos, boolean defaultRetained) { - this(defaultQos, defaultRetained, StandardCharsets.UTF_8.name()); - } - - /** - * Construct a converter with default options (qos=0, retain=false) and - * the supplied charset. - * @param charset the charset used to convert outbound String payloads to {@code byte[]} and inbound - * {@code byte[]} to String (unless {@link #setPayloadAsBytes(boolean) payloadAdBytes} is true). - * @since 4.1.2 - */ - public DefaultPahoMessageConverter(String charset) { - this(0, false, charset); - } - - /** - * Construct a converter to create outbound messages with the supplied default qos and - * retain settings and the supplied charset. - * @param defaultQos the default qos. - * @param defaultRetained the default retained. - * @param charset the charset used to convert outbound String payloads to - * {@code byte[]} and inbound {@code byte[]} to String (unless - * {@link #setPayloadAsBytes(boolean) payloadAdBytes} is true). - */ - public DefaultPahoMessageConverter(int defaultQos, boolean defaultRetained, String charset) { - this(defaultQos, MqttMessageConverter.defaultQosProcessor(), defaultRetained, - MqttMessageConverter.defaultRetainedProcessor(), charset); - } - - /** - * Construct a converter to create outbound messages with the supplied default qos and - * retained message processors and a UTF-8 charset for converting outbound String payloads to - * {@code byte[]} and inbound {@code byte[]} to String (unless - * {@link #setPayloadAsBytes(boolean) payloadAdBytes} is true). - * @param defaultQos the default qos. - * @param qosProcessor a message processor to determine the qos. - * @param defaultRetained the default retained. - * @param retainedProcessor a message processor to determine the retained flag. - * @since 5.0 - */ - public DefaultPahoMessageConverter(int defaultQos, MessageProcessor qosProcessor, boolean defaultRetained, - MessageProcessor retainedProcessor) { - - this(defaultQos, qosProcessor, defaultRetained, retainedProcessor, StandardCharsets.UTF_8.name()); - } - - /** - * Construct a converter to create outbound messages with the supplied default qos and - * retain settings and the supplied charset. - * @param defaultQos the default qos. - * @param qosProcessor a message processor to determine the qos. - * @param defaultRetained the default retained. - * @param retainedProcessor a message processor to determine the retained flag. - * @param charset the charset used to convert outbound String payloads to - * {@code byte[]} and inbound {@code byte[]} to String (unless - * {@link #setPayloadAsBytes(boolean) payloadAdBytes} is true). - * @since 5.0 - */ - public DefaultPahoMessageConverter(int defaultQos, MessageProcessor qosProcessor, boolean defaultRetained, - MessageProcessor retainedProcessor, String charset) { - - Assert.notNull(qosProcessor, "'qosProcessor' cannot be null"); - Assert.notNull(retainedProcessor, "'retainedProcessor' cannot be null"); - this.defaultQos = defaultQos; - this.qosProcessor = qosProcessor; - this.defaultRetained = defaultRetained; - this.retainedProcessor = retainedProcessor; - this.charset = Charset.forName(charset); - } - - @Override - public final void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - protected BeanFactory getBeanFactory() { - return this.beanFactory; - } - - protected MessageBuilderFactory getMessageBuilderFactory() { - if (!this.messageBuilderFactorySet) { - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - this.messageBuilderFactorySet = true; - } - return this.messageBuilderFactory; - } - - /** - * True if the converter should not convert the message payload to a String. - * Ignored if a {@link BytesMessageMapper} is provided. - * @param payloadAsBytes The payloadAsBytes to set. - * @see #setBytesMessageMapper(BytesMessageMapper) - */ - public void setPayloadAsBytes(boolean payloadAsBytes) { - this.payloadAsBytes = payloadAsBytes; - } - - public boolean isPayloadAsBytes() { - return this.payloadAsBytes; - } - - /** - * Set a {@link BytesMessageMapper} to use when mapping byte[]. - * {@link #setPayloadAsBytes(boolean)} is ignored when a {@link BytesMessageMapper} - * is provided. - * @param bytesMessageMapper the mapper. - * @since 5.0 - * @see #setPayloadAsBytes(boolean) - */ - public void setBytesMessageMapper(BytesMessageMapper bytesMessageMapper) { - this.bytesMessageMapper = bytesMessageMapper; - } - - @Override - public @Nullable Message toMessage(Object mqttMessage, @Nullable MessageHeaders headers) { - Assert.isInstanceOf(MqttMessage.class, mqttMessage, - () -> "This converter can only convert an 'MqttMessage'; received: " - + mqttMessage.getClass().getName()); - return toMessage(null, (MqttMessage) mqttMessage); - } - - @Override - public AbstractIntegrationMessageBuilder toMessageBuilder(@Nullable String topic, MqttMessage mqttMessage) { - try { - AbstractIntegrationMessageBuilder messageBuilder; - if (this.bytesMessageMapper != null) { - messageBuilder = - getMessageBuilderFactory() - .fromMessage(this.bytesMessageMapper.toMessage(mqttMessage.getPayload())); - } - else { - messageBuilder = - getMessageBuilderFactory() - .withPayload(mqttBytesToPayload(mqttMessage)); - } - messageBuilder - .setHeader(MqttHeaders.ID, mqttMessage.getId()) - .setHeader(MqttHeaders.RECEIVED_QOS, mqttMessage.getQos()) - .setHeader(MqttHeaders.DUPLICATE, mqttMessage.isDuplicate()) - .setHeader(MqttHeaders.RECEIVED_RETAINED, mqttMessage.isRetained()); - if (topic != null) { - messageBuilder.setHeader(MqttHeaders.RECEIVED_TOPIC, topic); - } - return messageBuilder; - } - catch (Exception e) { - throw new MessageConversionException("failed to convert object to Message", e); - } - } - - @Override - public MqttMessage fromMessage(Message message, Class targetClass) { - byte[] payloadBytes = messageToMqttBytes(message); - MqttMessage mqttMessage = new MqttMessage(payloadBytes); - Integer qos = this.qosProcessor.processMessage(message); - mqttMessage.setQos(qos == null ? this.defaultQos : qos); - Boolean retained = this.retainedProcessor.processMessage(message); - mqttMessage.setRetained(retained == null ? this.defaultRetained : retained); - return mqttMessage; - } - - /** - * Subclasses can override this method to convert the byte[] to a payload. - * The default implementation creates a String (default) or byte[]. - * @param mqttMessage The inbound message. - * @return The payload for the Spring integration message - */ - protected Object mqttBytesToPayload(MqttMessage mqttMessage) { - if (this.payloadAsBytes) { - return mqttMessage.getPayload(); - } - else { - return new String(mqttMessage.getPayload(), this.charset); - } - } - - /** - * Subclasses can override this method to convert the payload to a byte[]. - * The default implementation accepts a byte[] or String payload. - * If a {@link BytesMessageMapper} is provided, conversion to byte[] - * is delegated to it, so any payload that it can handle is supported. - * @param message The outbound Message. - * @return The byte[] which will become the payload of the MQTT Message. - */ - protected byte[] messageToMqttBytes(Message message) { - if (this.bytesMessageMapper != null) { - try { - return this.bytesMessageMapper.fromMessage(message); - } - catch (Exception e) { - throw new IllegalStateException("Failed to map outbound message", e); - } - } - else { - Object payload = message.getPayload(); - Assert.isTrue(payload instanceof byte[] || payload instanceof String, - () -> "This default converter can only handle 'byte[]' or 'String' payloads; consider adding a " - + "transformer to your flow definition, or provide a BytesMessageMapper, " - + "or subclass this converter for " - + payload.getClass().getName() + " payloads"); - byte[] payloadBytes; - if (payload instanceof String) { - payloadBytes = ((String) payload).getBytes(this.charset); - } - else { - payloadBytes = (byte[]) payload; - } - return payloadBytes; - } - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaderAccessor.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaderAccessor.java deleted file mode 100644 index 03c5dd1bd86..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaderAccessor.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.support; - -import org.jspecify.annotations.Nullable; - -import org.springframework.messaging.Message; - -/** - * Helper for typed access to incoming MQTT message headers. - * - * @author Gary Russell - * @since 5.3 - * - */ -public final class MqttHeaderAccessor { - - private MqttHeaderAccessor() { - } - - /** - * Return the received topic header. - * @param message the message. - * @return the header. - */ - @Nullable - public static String receivedTopic(Message message) { - return message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC, String.class); - } - - /** - * Return the MQTT message id. - * @param message the message. - * @return the header. - */ - @Nullable - public static Integer id(Message message) { - return message.getHeaders().get(MqttHeaders.ID, Integer.class); - } - - /** - * Return the received QOS header. - * @param message the message. - * @return the header. - */ - @Nullable - public static Integer receivedQos(Message message) { - return message.getHeaders().get(MqttHeaders.RECEIVED_QOS, Integer.class); - } - - /** - * Return the received retained header. - * @param message the message. - * @return the header. - */ - @Nullable - public static Boolean receivedRetained(Message message) { - return message.getHeaders().get(MqttHeaders.RECEIVED_RETAINED, Boolean.class); - } - - /** - * Return the duplicate header. - * @param message the message. - * @return the header. - */ - @Nullable - public static Boolean duplicate(Message message) { - return message.getHeaders().get(MqttHeaders.DUPLICATE, Boolean.class); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaderMapper.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaderMapper.java deleted file mode 100644 index d04792aeb31..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaderMapper.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.support; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.eclipse.paho.mqttv5.common.packet.MqttProperties; -import org.eclipse.paho.mqttv5.common.packet.UserProperty; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogAccessor; -import org.springframework.core.log.LogMessage; -import org.springframework.integration.mapping.HeaderMapper; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.Assert; -import org.springframework.util.MimeType; -import org.springframework.util.PatternMatchUtils; - -/** - * The default {@link HeaderMapper} implementation for MQTT v5 message properties mapping. - * - * @author Artem Bilan - * - * @since 5.5.5 - */ -public class MqttHeaderMapper implements HeaderMapper { - - private static final LogAccessor LOGGER = new LogAccessor(MqttHeaderMapper.class); - - private String[] inboundHeaderNames = {"*"}; - - private String[] outboundHeaderNames = { - MessageHeaders.CONTENT_TYPE, - MqttHeaders.MESSAGE_EXPIRY_INTERVAL, - MqttHeaders.RESPONSE_TOPIC, - MqttHeaders.CORRELATION_DATA - }; - - /** - * Provide a list of patterns to map MQTT message properties into message headers. - * By default, it maps all valid MQTT PUBLISH packet headers - * (see {@link org.eclipse.paho.mqttv5.common.packet.MqttPublish}), including all the user properties. - * @param inboundHeaderNames the MQTT message property patterns to map. - */ - public void setInboundHeaderNames(String... inboundHeaderNames) { - Assert.notNull(inboundHeaderNames, "'inboundHeaderNames' must not be null"); - String[] copy = Arrays.copyOf(inboundHeaderNames, inboundHeaderNames.length); - Arrays.sort(copy); - this.inboundHeaderNames = copy; - } - - /** - * Provide a list of patterns to map header into a PUBLISH MQTT message. - * Default headers are: - * {@link MessageHeaders#CONTENT_TYPE}, {@link MqttHeaders#MESSAGE_EXPIRY_INTERVAL}, - * {@link MqttHeaders#RESPONSE_TOPIC}, {@link MqttHeaders#CORRELATION_DATA}. - * @param outboundHeaderNames the header patterns to map. - */ - public void setOutboundHeaderNames(String... outboundHeaderNames) { - Assert.notNull(outboundHeaderNames, "'outboundHeaderNames' must not be null"); - String[] copy = Arrays.copyOf(outboundHeaderNames, outboundHeaderNames.length); - Arrays.sort(copy); - this.outboundHeaderNames = copy; - } - - @Override - public void fromHeaders(MessageHeaders headers, MqttProperties target) { - for (Map.Entry entry : headers.entrySet()) { - String name = entry.getKey(); - if (shouldMapHeader(name, this.outboundHeaderNames)) { - Object value = entry.getValue(); - if (value != null) { - setMqttHeader(target, name, value); - } - } - } - } - - @Override - public Map toHeaders(MqttProperties source) { - Map headers = new HashMap<>(); - if (source.getPayloadFormat()) { - headers.compute(MessageHeaders.CONTENT_TYPE, (k, v) -> mapPropertyIfMatch(k, source.getContentType())); - } - headers.compute(MqttHeaders.TOPIC_ALIAS, (k, v) -> mapPropertyIfMatch(k, source.getTopicAlias())); - headers.compute(MqttHeaders.RESPONSE_TOPIC, (k, v) -> mapPropertyIfMatch(k, source.getResponseTopic())); - headers.compute(MqttHeaders.CORRELATION_DATA, (k, v) -> mapPropertyIfMatch(k, source.getCorrelationData())); - - List userProperties = source.getUserProperties(); - for (UserProperty userProperty : userProperties) { - String name = userProperty.getKey(); - if (shouldMapHeader(name, this.inboundHeaderNames)) { - headers.put(name, userProperty.getValue()); - } - } - return headers; - } - - private @Nullable Object mapPropertyIfMatch(String headerName, @Nullable Object value) { - return (value != null && shouldMapHeader(headerName, this.inboundHeaderNames)) ? value : null; - } - - private static boolean shouldMapHeader(String headerName, String[] patterns) { - for (String pattern : patterns) { - if (PatternMatchUtils.simpleMatch(pattern, headerName)) { - LOGGER.debug(LogMessage.format("headerName=[%s] WILL be mapped, matched pattern=%s", - headerName, pattern)); - return true; - } - } - LOGGER.debug(LogMessage.format("headerName=[%s] WILL NOT be mapped", headerName)); - return false; - } - - private static void setMqttHeader(MqttProperties target, String name, Object value) { - switch (name) { - case MessageHeaders.CONTENT_TYPE: - setContentType(target, value); - target.setPayloadFormat(true); - break; - case MqttHeaders.MESSAGE_EXPIRY_INTERVAL: - setMessageExpiryInterval(target, value); - break; - case MqttHeaders.RESPONSE_TOPIC: - setResponseTopic(target, value); - break; - case MqttHeaders.CORRELATION_DATA: - setCorrelationData(target, value); - break; - default: - if (value instanceof String) { - target.getUserProperties().add(new UserProperty(name, (String) value)); - } - else if (value != null) { - throw new IllegalArgumentException( - "Expected String value for MQTT user properties, but received: " + value.getClass()); - } - } - } - - private static void setContentType(MqttProperties target, Object value) { - if (value instanceof MimeType) { - target.setContentType(((MimeType) value).toString()); - } - else if (value instanceof String) { - target.setContentType((String) value); - } - else { - throw new IllegalArgumentException( - "Expected MediaType or String value for 'content-type' header value, but received: " - + value.getClass()); - } - } - - private static void setMessageExpiryInterval(MqttProperties target, Object value) { - if (value instanceof Long) { - target.setMessageExpiryInterval((Long) value); - } - else if (value instanceof String) { - target.setMessageExpiryInterval(Long.parseLong((String) value)); - } - else { - throw new IllegalArgumentException( - "Expected Long or String value for 'mqtt_messageExpiryInterval' header value, but received: " - + value.getClass()); - } - } - - private static void setResponseTopic(MqttProperties target, Object value) { - if (value instanceof String) { - target.setResponseTopic((String) value); - } - else { - throw new IllegalArgumentException( - "Expected String value for 'mqtt_responseTopic' header value, but received: " + value.getClass()); - } - } - - private static void setCorrelationData(MqttProperties target, Object value) { - if (value instanceof byte[]) { - target.setCorrelationData((byte[]) value); - } - else if (value instanceof String) { - target.setCorrelationData(((String) value).getBytes(StandardCharsets.UTF_8)); - } - else { - throw new IllegalArgumentException( - "Expected byte[] or String value for 'mqtt_correlationData' header value, but received: " - + value.getClass()); - } - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaders.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaders.java deleted file mode 100644 index 54bfc5c67c4..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttHeaders.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.support; - -/** - * Spring Integration headers. - * - * @author Gary Russell - * - * @since 4.0 - * - */ -public final class MqttHeaders { - - public static final String PREFIX = "mqtt_"; - - public static final String QOS = PREFIX + "qos"; - - public static final String ID = PREFIX + "id"; - - public static final String RECEIVED_QOS = PREFIX + "receivedQos"; - - public static final String DUPLICATE = PREFIX + "duplicate"; - - public static final String RETAINED = PREFIX + "retained"; - - public static final String RECEIVED_RETAINED = PREFIX + "receivedRetained"; - - public static final String TOPIC = PREFIX + "topic"; - - public static final String RECEIVED_TOPIC = PREFIX + "receivedTopic"; - - public static final String MESSAGE_EXPIRY_INTERVAL = PREFIX + "messageExpiryInterval"; - - public static final String TOPIC_ALIAS = PREFIX + "topicAlias"; - - public static final String RESPONSE_TOPIC = PREFIX + "responseTopic"; - - public static final String CORRELATION_DATA = PREFIX + "correlationData"; - - private MqttHeaders() { - throw new AssertionError(); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttMessageConverter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttMessageConverter.java deleted file mode 100644 index 3c8a3863e26..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttMessageConverter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.support; - -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.converter.MessageConverter; - -/** - * Extension of {@link MessageConverter} allowing the topic to be added as - * a header. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -public interface MqttMessageConverter extends MessageConverter { - - /** - * Convert to a Message. - * The default implementation calls {@link #toMessageBuilder(String, MqttMessage)}. - * @param topic the topic. - * @param mqttMessage the MQTT message. - * @return the Message. - */ - default @Nullable Message toMessage(@Nullable String topic, MqttMessage mqttMessage) { - AbstractIntegrationMessageBuilder builder = toMessageBuilder(topic, mqttMessage); - if (builder != null) { - return builder.build(); - } - else { - return null; - } - } - - /** - * Convert to a message builder. - * @param topic the topic. - * @param mqttMessage the MQTT message. - * @return the builder. - */ - @Nullable AbstractIntegrationMessageBuilder toMessageBuilder(@Nullable String topic, MqttMessage mqttMessage); - - static MessageProcessor defaultQosProcessor() { - return message -> message.getHeaders().get(MqttHeaders.QOS, Integer.class); - } - - static MessageProcessor defaultRetainedProcessor() { - return message -> message.getHeaders().get(MqttHeaders.RETAINED, Boolean.class); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttUtils.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttUtils.java deleted file mode 100644 index 6f02674c094..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/MqttUtils.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.support; - -import java.lang.reflect.Method; -import java.util.Objects; - -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeanUtils; -import org.springframework.integration.JavaUtils; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -/** - * MQTT Utilities. - * - * @author Gary Russell - * - * @since 5.4 - * - */ -public final class MqttUtils { - - private static final boolean PAHO_MQTTV3_PRESENT = - ClassUtils.isPresent("org.eclipse.paho.client.mqttv3.MqttAsyncClient", null); - - private static final boolean PAHO_MQTTV5_PRESENT = - ClassUtils.isPresent("org.eclipse.paho.mqttv5.client.MqttAsyncClient", null); - - private static final @Nullable Method V3_STOP_RECONNECT_CYCLE_METHOD; - - private static final @Nullable Method V5_STOP_RECONNECT_CYCLE_METHOD; - - static { - if (PAHO_MQTTV3_PRESENT) { - V3_STOP_RECONNECT_CYCLE_METHOD = - ReflectionUtils.findMethod(org.eclipse.paho.client.mqttv3.MqttAsyncClient.class, - "stopReconnectCycle"); - JavaUtils.INSTANCE.acceptIfNotNull(V3_STOP_RECONNECT_CYCLE_METHOD, ReflectionUtils::makeAccessible); - } - else { - V3_STOP_RECONNECT_CYCLE_METHOD = null; - } - - if (PAHO_MQTTV5_PRESENT) { - V5_STOP_RECONNECT_CYCLE_METHOD = - ReflectionUtils.findMethod(org.eclipse.paho.mqttv5.client.MqttAsyncClient.class, - "stopReconnectCycle"); - JavaUtils.INSTANCE.acceptIfNotNull(V5_STOP_RECONNECT_CYCLE_METHOD, ReflectionUtils::makeAccessible); - } - else { - V5_STOP_RECONNECT_CYCLE_METHOD = null; - } - } - - private MqttUtils() { - } - - /** - * Clone the {@link MqttConnectOptions}, except the serverUris. - * @param options the options to clone. - * @return the clone. - */ - public static MqttConnectOptions cloneConnectOptions(MqttConnectOptions options) { - MqttConnectOptions options2 = new MqttConnectOptions(); - BeanUtils.copyProperties(options, options2, "password", "serverURIs"); - if (options.getPassword() != null) { - options2.setPassword(options.getPassword()); - } - return options2; - } - - /** - * Perform a {@code stopReconnectCycle()} (via reflection) method on the provided client - * to clean up resources on client stop. - * TODO until the real fix in Paho library. - * @param client the MQTTv3 Paho client instance. - * @since 6.1.9 - */ - public static void stopClientReconnectCycle(org.eclipse.paho.client.mqttv3.IMqttAsyncClient client) { - ReflectionUtils.invokeMethod(Objects.requireNonNull(V3_STOP_RECONNECT_CYCLE_METHOD), client); - } - - /** - * Perform a {@code stopReconnectCycle()} (via reflection) method on the provided client - * to clean up resources on client stop. - * TODO until the real fix in Paho library. - * @param client the MQTTv5 Paho client instance. - * @since 6.1.9 - */ - public static void stopClientReconnectCycle(org.eclipse.paho.mqttv5.client.IMqttAsyncClient client) { - ReflectionUtils.invokeMethod(Objects.requireNonNull(V5_STOP_RECONNECT_CYCLE_METHOD), client); - } - -} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/package-info.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/package-info.java deleted file mode 100644 index d3ebffe1046..00000000000 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/support/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides various support classes used across Spring Integration MqttAdapter Components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.mqtt.support; diff --git a/spring-integration-mqtt/src/main/resources/META-INF/spring.handlers b/spring-integration-mqtt/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index fdbf583f7f8..00000000000 --- a/spring-integration-mqtt/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/mqtt=org.springframework.integration.mqtt.config.xml.MqttNamespaceHandler diff --git a/spring-integration-mqtt/src/main/resources/META-INF/spring.schemas b/spring-integration-mqtt/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index 4421d6b7d93..00000000000 --- a/spring-integration-mqtt/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,16 +0,0 @@ -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.0.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.1.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.2.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.3.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-5.0.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-5.1.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-5.2.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -http\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.0.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.1.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.2.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-4.3.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-5.0.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-5.1.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt-5.2.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd -https\://www.springframework.org/schema/integration/mqtt/spring-integration-mqtt.xsd=org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd diff --git a/spring-integration-mqtt/src/main/resources/META-INF/spring.tooling b/spring-integration-mqtt/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index a6ee10e1019..00000000000 --- a/spring-integration-mqtt/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration mqtt namespace -http\://www.springframework.org/schema/integration/mqttadapter@name=integration mqtt Namespace -http\://www.springframework.org/schema/integration/mqttadapter@prefix=int-mqtt -http\://www.springframework.org/schema/integration/mqttadapter@icon=org/springframework/integration/config/xml/spring-integration-mqtt.gif diff --git a/spring-integration-mqtt/src/main/resources/META-INF/spring/aot.factories b/spring-integration-mqtt/src/main/resources/META-INF/spring/aot.factories deleted file mode 100644 index a7392134fd3..00000000000 --- a/spring-integration-mqtt/src/main/resources/META-INF/spring/aot.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.integration.mqtt.aot.MqttRuntimeHints diff --git a/spring-integration-mqtt/src/main/resources/org/springframework/integration/mqtt/config/spring-integration-mqtt.gif b/spring-integration-mqtt/src/main/resources/org/springframework/integration/mqtt/config/spring-integration-mqtt.gif deleted file mode 100644 index 210e0764fa4..00000000000 Binary files a/spring-integration-mqtt/src/main/resources/org/springframework/integration/mqtt/config/spring-integration-mqtt.gif and /dev/null differ diff --git a/spring-integration-mqtt/src/main/resources/org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd b/spring-integration-mqtt/src/main/resources/org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd deleted file mode 100644 index 3d18126d39f..00000000000 --- a/spring-integration-mqtt/src/main/resources/org/springframework/integration/mqtt/config/spring-integration-mqtt.xsd +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - - - - Defines a Message Producing Endpoint for the - 'org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter' that - subscribes to the MQTT topic(s) and produces messages to the channel. - - - - - - - - - - - - - - - - - Specifies one or more (comma-delimited) topics on which to listen for messages. - - - - - - - Specifies the QoS to use when subscribing to topics; default '1'. This can be single - value (applying to all topics); otherwise it must be a comma-delimited list corresponding - to the provided topics (the name number of elements must be provided). - - - - - - - - - - - - - - - - - - If a downstream exception is thrown and an error-channel is specified, - the MessagingException will be sent to this channel. Otherwise, any such exception - will be logged. - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.mqtt.outbound.MqttPahoMessageDrivenChannelAdapter' - that sends messages to the MQTT topic. - - - - - - - - - - - - - Channel from which messages will be output. - When a message is sent to this channel it will - cause the query - to be executed. - - - - - - - - - - - Specifies the order for invocation when this endpoint is connected as a - subscriber to a SubscribableChannel. - - - - - - - Specifies the default topic to which messages will be sent. Required if - the 'topic-expression' evaluates to 'null' - - - - - - - Specifies an expression to evaluate to determine the destination topic. - Default "headers['mqtt_topic']". - - - - - - - Specifies the default quality of service; used if the 'qos-expression' - evaluates to 'null'. Default 0. - - - - - - - Specifies an expression to evaluate to determine the message qos. - Default "headers['mqtt_qos']". - - - - - - - Specifies the default value of the 'retained' flag; used if the - 'retained-expression' evaluates to 'null'. Default false. - - - - - - - Specifies an expression to evaluate to determine the message 'retained' - flag. Default "headers['mqtt_retained']". - - - - - - - Specifies that sends should not block, with the thread returning - immediately the message is sent. When 'true', message - sent and message delivery events can be published; see 'async-events'. - Default: 'false'. - - - - - - - When 'async' is true, specifies that message - sent and message delivery events will be published for reception - by a suitably configured 'ApplicationListener' or an event - inbound-channel-adapter. - Default: 'false'. - - - - - - - - - - - Identifies the underlying Spring bean definition, which is an - instance of either 'EventDrivenConsumer' or 'PollingConsumer', - depending on whether the component's input channel is a - 'SubscribableChannel' or 'PollableChannel'. - - - - - - - Flag to indicate that the component should start automatically - on startup (default true). - - - - - - - - - - Flag to indicate the phase in which the component should start automatically - on startup. See SmartLifecycle. - - - - - - - - - - MQTT broker URL. - - - - - - - MQTT client ID. - - - - - - - to/from - a paho MqttMessage. Default is DefaultMqttMessageConverter. - ]]> - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/BackToBackAdapterTests-context.xml b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/BackToBackAdapterTests-context.xml deleted file mode 100644 index f5f3bbfa640..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/BackToBackAdapterTests-context.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/BackToBackAdapterTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/BackToBackAdapterTests.java deleted file mode 100644 index 4872eff12aa..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/BackToBackAdapterTests.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.io.File; -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.eclipse.paho.client.mqttv3.MqttClientPersistence; -import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationListener; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.core.MqttPahoComponent; -import org.springframework.integration.mqtt.event.MqttMessageDeliveredEvent; -import org.springframework.integration.mqtt.event.MqttMessageSentEvent; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.json.EmbeddedHeadersJsonMessageMapper; -import org.springframework.integration.support.json.JacksonMessagingUtils; -import org.springframework.integration.test.condition.LongRunningTest; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mock; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Glenn Renfro - * - * @since 4.0 - * - */ -@LongRunningTest -@SpringJUnitConfig -@DirtiesContext -public class BackToBackAdapterTests implements MosquittoContainerTest { - - private static final long QUIESCENT_TIMEOUT = 1; - - private static final long DISCONNECT_COMPLETION_TIMEOUT = 1L; - - @TempDir - static File folder; - - static ThreadPoolTaskScheduler taskScheduler; - - @Autowired - private MessageChannel out; - - @Autowired - private PollableChannel in; - - @Autowired - private EventsListener listener; - - @BeforeAll - static void setup() { - taskScheduler = new ThreadPoolTaskScheduler(); - taskScheduler.initialize(); - } - - @AfterAll - static void teardown() { - taskScheduler.destroy(); - } - - @Test - public void testSingleTopic() { - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-foo"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - adapter.start(); - MqttPahoMessageDrivenChannelAdapter inbound = - new MqttPahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), "si-test-in", "mqtt-foo"); - QueueChannel outputChannel = new QueueChannel(); - initializeInboundAdapter(inbound, outputChannel); - inbound.afterPropertiesSet(); - inbound.start(); - adapter.handleMessage(new GenericMessage<>("foo")); - Message out = outputChannel.receive(20000); - assertThat(out).isNotNull(); - adapter.stop(); - inbound.stop(); - assertThat(out.getPayload()).isEqualTo("foo"); - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-foo"); - assertThat(adapter.getConnectionInfo().getServerURIs()[0]).isEqualTo(MosquittoContainerTest.mqttUrl()); - } - - @Test - public void testJson() { - testJsonCommon("org.springframework"); - } - - @Test - public void testJsonNoTrust() { - testJsonCommon(); - } - - private void testJsonCommon(String... trusted) { - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-foo"); - adapter.setBeanFactory(mock(BeanFactory.class)); - EmbeddedHeadersJsonMessageMapper mapper = new EmbeddedHeadersJsonMessageMapper( - JacksonMessagingUtils.messagingAwareMapper("org.springframework")); - DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter(); - converter.setBytesMessageMapper(mapper); - converter.setBeanFactory(mock(BeanFactory.class)); - adapter.setConverter(converter); - adapter.afterPropertiesSet(); - adapter.start(); - MqttPahoMessageDrivenChannelAdapter inbound = - new MqttPahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), "si-test-in", "mqtt-foo"); - QueueChannel outputChannel = new QueueChannel(); - initializeInboundAdapter(inbound, outputChannel); - inbound.setConverter(converter); - inbound.afterPropertiesSet(); - inbound.start(); - adapter.handleMessage(new GenericMessage<>(new Foo("bar"), Collections.singletonMap("baz", "qux"))); - Message out = outputChannel.receive(20000); - assertThat(out).isNotNull(); - adapter.stop(); - inbound.stop(); - if (trusted != null) { - assertThat(out.getPayload()).isEqualTo(new Foo("bar")); - } - else { - assertThat(out.getPayload()).isNotEqualTo(new Foo("bar")); - } - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-foo"); - assertThat(out.getHeaders().get("baz")).isEqualTo("qux"); - } - - @Test - public void testAddRemoveTopic() { - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-foo"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - adapter.start(); - MqttPahoMessageDrivenChannelAdapter inbound = - new MqttPahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), "si-test-in"); - QueueChannel outputChannel = new QueueChannel(); - initializeInboundAdapter(inbound, outputChannel); - inbound.afterPropertiesSet(); - inbound.start(); - inbound.addTopic("mqtt-foo"); - adapter.handleMessage(new GenericMessage<>("foo")); - Message out = outputChannel.receive(20_000); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo("foo"); - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-foo"); - - inbound.addTopic("mqtt-bar"); - adapter.handleMessage(MessageBuilder.withPayload("bar").setHeader(MqttHeaders.TOPIC, "mqtt-bar").build()); - out = outputChannel.receive(20_000); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo("bar"); - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-bar"); - - inbound.removeTopic("mqtt-bar"); - adapter.handleMessage(MessageBuilder.withPayload("bar").setHeader(MqttHeaders.TOPIC, "mqtt-bar").build()); - out = outputChannel.receive(1); - assertThat(out).isNull(); - - assertThatExceptionOfType(MessagingException.class) - .isThrownBy(() -> inbound.addTopic("mqtt-foo")) - .withMessage("Topic 'mqtt-foo' is already subscribed."); - - inbound.addTopic("mqqt-bar", "mqqt-baz"); - inbound.removeTopic("mqqt-bar", "mqqt-baz"); - inbound.addTopics(new String[] {"mqqt-bar", "mqqt-baz"}, new int[] {0, 0}); - inbound.removeTopic("mqqt-bar", "mqqt-baz"); - - adapter.stop(); - inbound.stop(); - } - - @Test - public void testTwoTopics() { - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-foo"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - adapter.start(); - MqttPahoMessageDrivenChannelAdapter inbound = - new MqttPahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), - "si-test-in", "mqtt-foo", "mqtt-bar"); - QueueChannel outputChannel = new QueueChannel(); - initializeInboundAdapter(inbound, outputChannel); - inbound.afterPropertiesSet(); - inbound.start(); - adapter.handleMessage(new GenericMessage<>("foo")); - Message message = MessageBuilder.withPayload("bar").setHeader(MqttHeaders.TOPIC, "mqtt-bar").build(); - adapter.handleMessage(message); - Message out = outputChannel.receive(20000); - assertThat(out).isNotNull(); - assertThat(out.getPayload()).isEqualTo("foo"); - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-foo"); - out = outputChannel.receive(20000); - assertThat(out).isNotNull(); - inbound.stop(); - assertThat(out.getPayload()).isEqualTo("bar"); - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-bar"); - - adapter.stop(); - } - - @Test - public void testAsync() throws Exception { - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-foo"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.setAsync(true); - adapter.setAsyncEvents(true); - EventPublisher publisher = new EventPublisher(); - adapter.setApplicationEventPublisher(publisher); - adapter.afterPropertiesSet(); - adapter.start(); - MqttPahoMessageDrivenChannelAdapter inbound = - new MqttPahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), "si-test-in", "mqtt-foo"); - QueueChannel outputChannel = new QueueChannel(); - initializeInboundAdapter(inbound, outputChannel); - inbound.afterPropertiesSet(); - inbound.start(); - GenericMessage message = new GenericMessage<>("foo"); - adapter.handleMessage(message); - verifyEvents(adapter, publisher, message); - Message out = outputChannel.receive(20000); - assertThat(out).isNotNull(); - adapter.stop(); - inbound.stop(); - - assertThat(out.getPayload()).isEqualTo("foo"); - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-foo"); - } - - @Test - public void testAsyncPersisted() throws Exception { - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); - MqttClientPersistence persistence = new MqttDefaultFilePersistence(folder.getAbsolutePath()); - factory.setPersistence(persistence); - MqttPahoMessageHandler adapter = - new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out", factory); - adapter.setDefaultTopic("mqtt-foo"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.setAsync(true); - adapter.setAsyncEvents(true); - adapter.setDefaultQos(1); - EventPublisher publisher1 = new EventPublisher(); - adapter.setApplicationEventPublisher(publisher1); - adapter.afterPropertiesSet(); - adapter.start(); - - MqttPahoMessageDrivenChannelAdapter inbound = - new MqttPahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), - "si-test-in", "mqtt-foo", "mqtt-bar"); - QueueChannel outputChannel = new QueueChannel(); - initializeInboundAdapter(inbound, outputChannel); - inbound.afterPropertiesSet(); - inbound.start(); - Message message1 = new GenericMessage<>("foo"); - adapter.handleMessage(message1); - verifyEvents(adapter, publisher1, message1); - - Message message2 = MessageBuilder.withPayload("bar") - .setHeader(MqttHeaders.TOPIC, "mqtt-bar") - .build(); - EventPublisher publisher2 = new EventPublisher(); - adapter.setApplicationEventPublisher(publisher2); - adapter.handleMessage(message2); - verifyEvents(adapter, publisher2, message2); - - verifyMessageIds(publisher1, publisher2); - int clientInstance = publisher1.delivered.getClientInstance(); - - adapter.stop(); - adapter.start(); // new client instance - - publisher1 = new EventPublisher(); - adapter.setApplicationEventPublisher(publisher1); - adapter.handleMessage(message1); - verifyEvents(adapter, publisher1, message1); - - publisher2 = new EventPublisher(); - adapter.setApplicationEventPublisher(publisher2); - adapter.handleMessage(message2); - verifyEvents(adapter, publisher2, message2); - - verifyMessageIds(publisher1, publisher2); - - assertThat(publisher1.delivered.getClientInstance()).isNotEqualTo(clientInstance); - - Message out; - for (int i = 0; i < 4; i++) { - out = outputChannel.receive(20000); - assertThat(out).isNotNull(); - if ("foo".equals(out.getPayload())) { - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-foo"); - } - else if ("bar".equals(out.getPayload())) { - assertThat(out.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)).isEqualTo("mqtt-bar"); - } - else { - fail("unexpected payload " + out.getPayload()); - } - } - adapter.stop(); - inbound.stop(); - } - - private void verifyEvents(MqttPahoMessageHandler adapter, EventPublisher publisher1, Message message1) - throws InterruptedException { - - assertThat(publisher1.latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(publisher1.sent).isNotNull(); - assertThat(publisher1.delivered).isNotNull(); - assertThat(publisher1.delivered.getMessageId()).isEqualTo(publisher1.sent.getMessageId()); - assertThat(publisher1.sent.getClientId()).isEqualTo(adapter.getClientId()); - assertThat(publisher1.delivered.getClientId()).isEqualTo(adapter.getClientId()); - assertThat(publisher1.sent.getClientInstance()).isEqualTo(adapter.getClientInstance()); - assertThat(publisher1.delivered.getClientInstance()).isEqualTo(adapter.getClientInstance()); - assertThat(publisher1.sent.getMessage()).isSameAs(message1); - } - - private void verifyMessageIds(EventPublisher publisher1, EventPublisher publisher2) { - assertThat(publisher2.delivered.getMessageId()).isNotEqualTo(publisher1.delivered.getMessageId()); - assertThat(publisher2.delivered.getClientId()).isEqualTo(publisher1.delivered.getClientId()); - assertThat(publisher2.delivered.getClientInstance()).isEqualTo(publisher1.delivered.getClientInstance()); - } - - @Test - public void testMultiURIs() { - out.send(new GenericMessage<>("foo")); - Message message = in.receive(20000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo("foo"); - MqttPahoComponent source = this.listener.event.getSourceAsType(); - assertThat(source.getConnectionInfo().getServerURIs()) - .contains(MosquittoContainerTest.mqttUrl(), MosquittoContainerTest.mqttUrl()); - } - - public static class EventsListener implements ApplicationListener { - - volatile MqttSubscribedEvent event; - - @Override - public void onApplicationEvent(MqttSubscribedEvent event) { - this.event = event; - } - - } - - private static void initializeInboundAdapter(MqttPahoMessageDrivenChannelAdapter inbound, QueueChannel outputChannel) { - inbound.setOutputChannel(outputChannel); - inbound.setTaskScheduler(taskScheduler); - inbound.setQuiescentTimeout(QUIESCENT_TIMEOUT); - inbound.setDisconnectCompletionTimeout(DISCONNECT_COMPLETION_TIMEOUT); - inbound.setBeanFactory(mock(BeanFactory.class)); - inbound.setApplicationEventPublisher(mock(ApplicationEventPublisher.class)); - } - - private class EventPublisher implements ApplicationEventPublisher { - - private volatile MqttMessageDeliveredEvent delivered; - - private MqttMessageSentEvent sent; - - private final CountDownLatch latch = new CountDownLatch(2); - - EventPublisher() { - super(); - } - - @Override - public void publishEvent(ApplicationEvent event) { - if (event instanceof MqttMessageSentEvent) { - this.sent = (MqttMessageSentEvent) event; - } - else if (event instanceof MqttMessageDeliveredEvent) { - this.delivered = (MqttMessageDeliveredEvent) event; - } - latch.countDown(); - } - - @Override - public void publishEvent(Object event) { - - } - - } - - public static class Foo { - - private String bar; - - public Foo() { - super(); - } - - public Foo(String bar) { - this.bar = bar; - } - - public String getBar() { - return this.bar; - } - - public void setBar(String bar) { - this.bar = bar; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.bar == null) ? 0 : this.bar.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Foo other = (Foo) obj; - if (this.bar == null) { - return other.bar == null; - } - else { - return this.bar.equals(other.bar); - } - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/ClientManagerBackToBackTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/ClientManagerBackToBackTests.java deleted file mode 100644 index db1c2f60305..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/ClientManagerBackToBackTests.java +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.junit.jupiter.api.Test; - -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.EventListener; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.context.IntegrationFlowContext; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.mqtt.core.Mqttv3ClientManager; -import org.springframework.integration.mqtt.core.Mqttv5ClientManager; -import org.springframework.integration.mqtt.event.MqttMessageDeliveryEvent; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.inbound.Mqttv5PahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.integration.mqtt.outbound.Mqttv5PahoMessageHandler; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -/** - * @author Artem Vozhdayenko - * @author Artem Bilan - * - * @since 6.0 - */ -class ClientManagerBackToBackTests implements MosquittoContainerTest { - - private static final long QUIESCENT_TIMEOUT = 1L; - - private static final long DISCONNECT_COMPLETION_TIMEOUT = 1L; - - @Test - void testSameV3ClientIdWorksForPubAndSub() throws Exception { - testSubscribeAndPublish(Mqttv3Config.class, Mqttv3Config.TOPIC_NAME, Mqttv3Config.subscribedLatch); - } - - @Test - void testSameV5ClientIdWorksForPubAndSub() throws Exception { - testSubscribeAndPublish(Mqttv5Config.class, Mqttv5Config.TOPIC_NAME, Mqttv5Config.subscribedLatch); - } - - @Test - void testV3ClientManagerReconnect() throws Exception { - testSubscribeAndPublish(Mqttv3ConfigWithDisconnect.class, Mqttv3ConfigWithDisconnect.TOPIC_NAME, - Mqttv3ConfigWithDisconnect.subscribedLatch); - } - - @Test - void testV3ClientManagerRuntime() throws Exception { - testSubscribeAndPublishRuntime(Mqttv3ConfigRuntime.class, Mqttv3ConfigRuntime.TOPIC_NAME, - Mqttv3ConfigRuntime.subscribedLatch); - } - - @Test - void testV5ClientManagerReconnect() throws Exception { - testSubscribeAndPublish(Mqttv5ConfigWithDisconnect.class, Mqttv5ConfigWithDisconnect.TOPIC_NAME, - Mqttv5ConfigWithDisconnect.subscribedLatch); - } - - @Test - void testV5ClientManagerRuntime() throws Exception { - testSubscribeAndPublishRuntime(Mqttv5ConfigRuntime.class, Mqttv5ConfigRuntime.TOPIC_NAME, - Mqttv5ConfigRuntime.subscribedLatch); - } - - @SuppressWarnings("unchecked") - private void testSubscribeAndPublish(Class configClass, String topicName, CountDownLatch subscribedLatch) - throws Exception { - - try (var ctx = new AnnotationConfigApplicationContext(configClass)) { - // given - var input = ctx.getBean("mqttOutFlow.input", MessageChannel.class); - var output = ctx.getBean("fromMqttChannel", PollableChannel.class); - String testPayload = "foo"; - assertThat(subscribedLatch.await(20, TimeUnit.SECONDS)).isTrue(); - - // when - input.send(MessageBuilder.withPayload(testPayload).setHeader(MqttHeaders.TOPIC, topicName).build()); - Message receive = output.receive(20_000); - - // then - assertThat(receive).isNotNull(); - Object payload = receive.getPayload(); - if (payload instanceof String sp) { - assertThat(sp).isEqualTo(testPayload); - } - else { - assertThat(payload).isEqualTo(testPayload.getBytes(StandardCharsets.UTF_8)); - } - - if (ctx.containsBean("deliveryEvents")) { - List deliveryEvents = ctx.getBean("deliveryEvents", List.class); - // MqttMessageSentEvent and MqttMessageDeliveredEvent - await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> assertThat(deliveryEvents).hasSize(2)); - } - } - } - - private void testSubscribeAndPublishRuntime(Class configClass, String topicName, CountDownLatch subscribedLatch) - throws Exception { - - try (var ctx = new AnnotationConfigApplicationContext(configClass)) { - // given - var input = ctx.getBean("mqttOutFlow.input", MessageChannel.class); - var flowContext = ctx.getBean(IntegrationFlowContext.class); - var factory = ctx.getBean(MessageDrivenChannelAdapterFactory.class); - var output = new QueueChannel(); - - flowContext.registration(IntegrationFlow - .from(factory.createMessageDrivenAdapter(ctx)) - .channel(output) - .get()).register(); - String testPayload = "foo"; - assertThat(subscribedLatch.await(20, TimeUnit.SECONDS)).isTrue(); - - // when - input.send(MessageBuilder.withPayload(testPayload).setHeader(MqttHeaders.TOPIC, topicName).build()); - Message receive = output.receive(20_000); - - // then - assertThat(receive).isNotNull(); - Object payload = receive.getPayload(); - if (payload instanceof String sp) { - assertThat(sp).isEqualTo(testPayload); - } - else { - assertThat(payload).isEqualTo(testPayload.getBytes(StandardCharsets.UTF_8)); - } - } - } - - @Configuration - @EnableIntegration - public static class Mqttv3Config { - - static final String TOPIC_NAME = "test-topic-v3"; - - static final CountDownLatch subscribedLatch = new CountDownLatch(1); - - @EventListener - public void onSubscribed(MqttSubscribedEvent e) { - subscribedLatch.countDown(); - } - - @EventListener - void mqttEvents(MqttMessageDeliveryEvent event) { - deliveryEvents().add(event); - } - - @Bean - List deliveryEvents() { - return new ArrayList<>(); - } - - @Bean - public Mqttv3ClientManager mqttv3ClientManager() { - MqttConnectOptions connectionOptions = new MqttConnectOptions(); - connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()}); - connectionOptions.setAutomaticReconnect(true); - Mqttv3ClientManager result = new Mqttv3ClientManager(connectionOptions, "client-manager-client-id-v3"); - result.setQuiescentTimeout(QUIESCENT_TIMEOUT); - result.setDisconnectCompletionTimeout(DISCONNECT_COMPLETION_TIMEOUT); - return result; - } - - @Bean - public IntegrationFlow mqttOutFlow(Mqttv3ClientManager mqttv3ClientManager) { - MqttPahoMessageHandler mqttPahoMessageHandler = new MqttPahoMessageHandler(mqttv3ClientManager); - mqttPahoMessageHandler.setAsync(true); - mqttPahoMessageHandler.setAsyncEvents(true); - return f -> f.handle(mqttPahoMessageHandler); - } - - @Bean - public IntegrationFlow mqttInFlow(Mqttv3ClientManager mqttv3ClientManager) { - return IntegrationFlow.from(new MqttPahoMessageDrivenChannelAdapter(mqttv3ClientManager, TOPIC_NAME)) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - } - - @Configuration - @EnableIntegration - public static class Mqttv3ConfigWithDisconnect { - - static final String TOPIC_NAME = "test-topic-v3-reconnect"; - - static final CountDownLatch subscribedLatch = new CountDownLatch(1); - - @EventListener - public void onSubscribed(MqttSubscribedEvent e) { - subscribedLatch.countDown(); - } - - @Bean - public ClientV3Disconnector disconnector(Mqttv3ClientManager clientManager) { - return new ClientV3Disconnector(clientManager); - } - - @Bean - public Mqttv3ClientManager mqttv3ClientManager() { - MqttConnectOptions connectionOptions = new MqttConnectOptions(); - connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()}); - connectionOptions.setAutomaticReconnect(true); - Mqttv3ClientManager result = new Mqttv3ClientManager(connectionOptions, "client-manager-client-id-v3-reconnect"); - result.setQuiescentTimeout(QUIESCENT_TIMEOUT); - result.setDisconnectCompletionTimeout(DISCONNECT_COMPLETION_TIMEOUT); - return result; - } - - @Bean - public IntegrationFlow mqttOutFlow() { - return f -> f.handle(new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "old-client-v3")); - } - - @Bean - public IntegrationFlow mqttInFlow(Mqttv3ClientManager mqttv3ClientManager) { - return IntegrationFlow.from(new MqttPahoMessageDrivenChannelAdapter(mqttv3ClientManager, TOPIC_NAME)) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - } - - @Configuration - @EnableIntegration - public static class Mqttv3ConfigRuntime implements MessageDrivenChannelAdapterFactory { - - static final String TOPIC_NAME = "test-topic-v3"; - - static final CountDownLatch subscribedLatch = new CountDownLatch(1); - - @EventListener - public void onSubscribed(MqttSubscribedEvent e) { - subscribedLatch.countDown(); - } - - @Bean - public Mqttv3ClientManager mqttv3ClientManager() { - MqttConnectOptions connectionOptions = new MqttConnectOptions(); - connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()}); - connectionOptions.setAutomaticReconnect(true); - Mqttv3ClientManager result = new Mqttv3ClientManager(connectionOptions, "client-manager-client-id-v3"); - result.setQuiescentTimeout(QUIESCENT_TIMEOUT); - result.setDisconnectCompletionTimeout(DISCONNECT_COMPLETION_TIMEOUT); - return result; - } - - @Bean - public IntegrationFlow mqttOutFlow(Mqttv3ClientManager mqttv3ClientManager) { - return f -> f.handle(new MqttPahoMessageHandler(mqttv3ClientManager)); - } - - @Override - public MessageProducerSupport createMessageDrivenAdapter(ApplicationContext ctx) { - var clientManager = ctx.getBean(Mqttv3ClientManager.class); - return new MqttPahoMessageDrivenChannelAdapter(clientManager, TOPIC_NAME); - } - - } - - @Configuration - @EnableIntegration - public static class Mqttv5Config { - - static final String TOPIC_NAME = "test-topic-v5"; - - static final CountDownLatch subscribedLatch = new CountDownLatch(1); - - @EventListener - public void onSubscribed(MqttSubscribedEvent e) { - subscribedLatch.countDown(); - } - - @EventListener - void mqttEvents(MqttMessageDeliveryEvent event) { - deliveryEvents().add(event); - } - - @Bean - List deliveryEvents() { - return new ArrayList<>(); - } - - @Bean - public Mqttv5ClientManager mqttv5ClientManager() { - return new Mqttv5ClientManager(MosquittoContainerTest.mqttUrl(), "client-manager-client-id-v5"); - } - - @Bean - @ServiceActivator(inputChannel = "mqttOutFlow.input") - public Mqttv5PahoMessageHandler mqttv5PahoMessageHandler(Mqttv5ClientManager mqttv5ClientManager) { - Mqttv5PahoMessageHandler mqttPahoMessageHandler = new Mqttv5PahoMessageHandler(mqttv5ClientManager); - mqttPahoMessageHandler.setAsync(true); - mqttPahoMessageHandler.setAsyncEvents(true); - return mqttPahoMessageHandler; - } - - @Bean - public IntegrationFlow mqttInFlow(Mqttv5ClientManager mqttv5ClientManager) { - return IntegrationFlow.from(new Mqttv5PahoMessageDrivenChannelAdapter(mqttv5ClientManager, TOPIC_NAME)) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - } - - @Configuration - @EnableIntegration - public static class Mqttv5ConfigWithDisconnect { - - static final String TOPIC_NAME = "test-topic-v5-reconnect"; - - static final CountDownLatch subscribedLatch = new CountDownLatch(1); - - @EventListener - public void onSubscribed(MqttSubscribedEvent e) { - subscribedLatch.countDown(); - } - - @Bean - public ClientV5Disconnector clientV3Disconnector(Mqttv5ClientManager clientManager) { - return new ClientV5Disconnector(clientManager); - } - - @Bean - public Mqttv5ClientManager mqttv5ClientManager() { - return new Mqttv5ClientManager(MosquittoContainerTest.mqttUrl(), "client-manager-client-id-v5-reconnect"); - } - - @Bean - public IntegrationFlow mqttOutFlow(Mqttv5ClientManager mqttv5ClientManager) { - return f -> f.handle(new Mqttv5PahoMessageHandler(MosquittoContainerTest.mqttUrl(), "old-client-v5")); - } - - @Bean - public IntegrationFlow mqttInFlow(Mqttv5ClientManager mqttv5ClientManager) { - return IntegrationFlow.from(new Mqttv5PahoMessageDrivenChannelAdapter(mqttv5ClientManager, TOPIC_NAME)) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - } - - @Configuration - @EnableIntegration - public static class Mqttv5ConfigRuntime implements MessageDrivenChannelAdapterFactory { - - static final String TOPIC_NAME = "test-topic-v5"; - - static final CountDownLatch subscribedLatch = new CountDownLatch(1); - - @EventListener - public void onSubscribed(MqttSubscribedEvent e) { - subscribedLatch.countDown(); - } - - @Bean - public Mqttv5ClientManager mqttv5ClientManager() { - return new Mqttv5ClientManager(MosquittoContainerTest.mqttUrl(), "client-manager-client-id-v5"); - } - - @Bean - @ServiceActivator(inputChannel = "mqttOutFlow.input") - public Mqttv5PahoMessageHandler mqttv5PahoMessageHandler(Mqttv5ClientManager mqttv5ClientManager) { - return new Mqttv5PahoMessageHandler(mqttv5ClientManager); - } - - @Override - public MessageProducerSupport createMessageDrivenAdapter(ApplicationContext ctx) { - var clientManager = ctx.getBean(Mqttv5ClientManager.class); - return new Mqttv5PahoMessageDrivenChannelAdapter(clientManager, TOPIC_NAME); - } - - } - - interface MessageDrivenChannelAdapterFactory { - - MessageProducerSupport createMessageDrivenAdapter(ApplicationContext ctx); - - } - - record ClientV3Disconnector(Mqttv3ClientManager clientManager) { - - @EventListener(MqttSubscribedEvent.class) - public void handleSubscribedEvent() { - try { - this.clientManager.getClient().disconnectForcibly(); - } - catch (MqttException ex) { - throw new IllegalStateException("could not disconnect the client!"); - } - } - - } - - record ClientV5Disconnector(Mqttv5ClientManager clientManager) { - - @EventListener(MqttSubscribedEvent.class) - public void handleSubscribedEvent() { - try { - this.clientManager.getClient().disconnectForcibly(); - } - catch (org.eclipse.paho.mqttv5.common.MqttException ex) { - throw new IllegalStateException("could not disconnect the client!"); - } - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/DownstreamExceptionTests-context.xml b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/DownstreamExceptionTests-context.xml deleted file mode 100644 index ad7463997ad..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/DownstreamExceptionTests-context.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/DownstreamExceptionTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/DownstreamExceptionTests.java deleted file mode 100644 index 46813b71aaf..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/DownstreamExceptionTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; - -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.log.LogAccessor; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class DownstreamExceptionTests implements MosquittoContainerTest { - - @Autowired - private Service service; - - @Autowired - private MqttPahoMessageDrivenChannelAdapter noErrorChannel; - - @Autowired - private MqttPahoMessageDrivenChannelAdapter withErrorChannel; - - @Autowired - private PollableChannel errors; - - @Test - @SuppressWarnings("unchecked") - public void testNoErrorChannel() throws Exception { - service.n = 0; - LogAccessor logger = spy(TestUtils.getPropertyValue(noErrorChannel, "logger", LogAccessor.class)); - final CountDownLatch latch = new CountDownLatch(1); - doAnswer(invocation -> { - if (((Supplier) invocation.getArgument(1)).get().contains("Unhandled")) { - latch.countDown(); - } - return null; - }).when(logger).error(any(Throwable.class), any(Supplier.class)); - new DirectFieldAccessor(noErrorChannel).setPropertyValue("logger", logger); - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-fooEx1"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - adapter.start(); - adapter.handleMessage(new GenericMessage<>("foo")); - service.barrier.await(10, TimeUnit.SECONDS); - service.barrier.reset(); - adapter.handleMessage(new GenericMessage<>("foo")); - service.barrier.await(10, TimeUnit.SECONDS); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - verify(logger).error(any(Throwable.class), - ArgumentMatchers.>argThat(logMessage -> - logMessage.get().startsWith("Unhandled exception for"))); - service.barrier.reset(); - adapter.stop(); - } - - @Test - public void testWithErrorChannel() throws Exception { - assertThat(TestUtils.getPropertyValue(this.withErrorChannel, "errorChannel")).isSameAs(this.errors); - service.n = 0; - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler(MosquittoContainerTest.mqttUrl(), "si-test-out"); - adapter.setDefaultTopic("mqtt-fooEx2"); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - adapter.start(); - adapter.handleMessage(new GenericMessage<>("foo")); - service.barrier.await(10, TimeUnit.SECONDS); - service.barrier.reset(); - adapter.handleMessage(new GenericMessage<>("foo")); - service.barrier.await(10, TimeUnit.SECONDS); - assertThat(errors.receive(10000)).isNotNull(); - service.barrier.reset(); - adapter.stop(); - } - - public static class Service { - - public CyclicBarrier barrier = new CyclicBarrier(2); - - public int n; - - public void foo(String foo) throws Exception { - barrier.await(10, TimeUnit.SECONDS); - if (n++ > 0) { - throw new RuntimeException("bar"); - } - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MosquittoContainerTest.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MosquittoContainerTest.java deleted file mode 100644 index dd536dd4da4..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MosquittoContainerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.junit.jupiter.Testcontainers; - -/** - * The base contract for JUnit tests based on the container for MQTT Mosquitto broker. - * The Testcontainers 'reuse' option must be disabled,so, Ryuk container is started - * and will clean all the containers up from this test suite after JVM exit. - * Since the Mosquitto container instance is shared via static property, it is going to be - * started only once per JVM, therefore the target Docker container is reused automatically. - * - * @author Artem Bilan - * - * @since 5.5.5 - */ -@Testcontainers(disabledWithoutDocker = true) -public interface MosquittoContainerTest { - - GenericContainer MOSQUITTO_CONTAINER = - new GenericContainer<>("eclipse-mosquitto:2.0.12") - .withCommand("mosquitto -c /mosquitto-no-auth.conf") - .withExposedPorts(1883); - - @BeforeAll - static void startContainer() { - MOSQUITTO_CONTAINER.start(); - } - - static String mqttUrl() { - return "tcp://localhost:" + MOSQUITTO_CONTAINER.getFirstMappedPort(); - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MqttAdapterTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MqttAdapterTests.java deleted file mode 100644 index db6a9bb381f..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MqttAdapterTests.java +++ /dev/null @@ -1,651 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.lang.reflect.Method; -import java.util.Properties; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import javax.net.SocketFactory; - -import org.aopalliance.intercept.MethodInterceptor; -import org.eclipse.paho.client.mqttv3.IMqttAsyncClient; -import org.eclipse.paho.client.mqttv3.IMqttToken; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.eclipse.paho.client.mqttv3.MqttToken; -import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; - -import org.springframework.aop.framework.ProxyFactoryBean; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.context.expression.MapAccessor; -import org.springframework.core.log.LogAccessor; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.core.MqttPahoClientFactory; -import org.springframework.integration.mqtt.core.Mqttv3ClientManager; -import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent; -import org.springframework.integration.mqtt.event.MqttIntegrationEvent; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; -import org.springframework.integration.mqtt.support.MqttHeaderAccessor; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * @author Glenn Renfro - * - * @since 4.0 - * - */ -public class MqttAdapterTests implements TestApplicationContextAware { - - private final IMqttToken alwaysComplete; - - { - ProxyFactoryBean pfb = new ProxyFactoryBean(); - pfb.addAdvice((MethodInterceptor) invocation -> null); - pfb.setInterfaces(IMqttToken.class); - this.alwaysComplete = (IMqttToken) pfb.getObject(); - } - - @Test - public void testCloseOnBadConnectOut() throws Exception { - final IMqttAsyncClient client = mock(IMqttAsyncClient.class); - MqttPahoMessageHandler adapter = buildAdapterOut(client); - willThrow(new MqttException(0)).given(client).connect(any()); - adapter.start(); - try { - adapter.handleMessage(new GenericMessage<>("foo")); - fail("exception expected"); - } - catch (MessageHandlingException e) { - // NOSONAR - } - verify(client).close(); - adapter.stop(); - } - - @Test - public void testOutboundOptionsApplied() throws Exception { - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setCleanSession(false); - connectOptions.setConnectionTimeout(23); - connectOptions.setKeepAliveInterval(45); - connectOptions.setPassword("pass".toCharArray()); - MemoryPersistence persistence = new MemoryPersistence(); - factory.setPersistence(persistence); - final SocketFactory socketFactory = SocketFactory.getDefault(); - connectOptions.setSocketFactory(socketFactory); - final Properties props = new Properties(); - connectOptions.setSSLProperties(props); - connectOptions.setUserName("user"); - connectOptions.setWill("foo", "bar".getBytes(), 2, true); - factory.setConnectionOptions(connectOptions); - - factory = spy(factory); - final MqttAsyncClient client = mock(MqttAsyncClient.class); - willAnswer(invocation -> client).given(factory).getAsyncClientInstance(anyString(), anyString()); - - MqttPahoMessageHandler handler = new MqttPahoMessageHandler("foo", "bar", factory); - handler.setDefaultTopic("mqtt-foo"); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - handler.start(); - - final MqttToken token = mock(MqttToken.class); - final AtomicBoolean connectCalled = new AtomicBoolean(); - willAnswer(invocation -> { - MqttConnectOptions options = invocation.getArgument(0); - assertThat(options.getConnectionTimeout()).isEqualTo(23); - assertThat(options.getKeepAliveInterval()).isEqualTo(45); - assertThat(new String(options.getPassword())).isEqualTo("pass"); - assertThat(options.getSocketFactory()).isSameAs(socketFactory); - assertThat(options.getSSLProperties()).isSameAs(props); - assertThat(options.getUserName()).isEqualTo("user"); - assertThat(options.getWillDestination()).isEqualTo("foo"); - assertThat(new String(options.getWillMessage().getPayload())).isEqualTo("bar"); - assertThat(options.getWillMessage().getQos()).isEqualTo(2); - connectCalled.set(true); - return token; - }).given(client).connect(any(MqttConnectOptions.class)); - willReturn(token).given(client).subscribe(any(String[].class), any(int[].class), any()); - - final MqttDeliveryToken deliveryToken = mock(MqttDeliveryToken.class); - final AtomicBoolean publishCalled = new AtomicBoolean(); - willAnswer(invocation -> { - assertThat(invocation.getArguments()[0]).isEqualTo("mqtt-foo"); - MqttMessage message = invocation.getArgument(1); - assertThat(new String(message.getPayload())).isEqualTo("Hello, world!"); - publishCalled.set(true); - return deliveryToken; - }).given(client).publish(anyString(), any(), any(), any()); - - handler.handleMessage(new GenericMessage<>("Hello, world!")); - - verify(client, times(1)).connect(any(MqttConnectOptions.class)); - assertThat(connectCalled.get()).isTrue(); - AtomicReference failed = new AtomicReference<>(); - handler.setApplicationEventPublisher(failed::set); - handler.connectionLost(new IllegalStateException()); - assertThat(failed.get()).isInstanceOf(MqttConnectionFailedEvent.class); - handler.stop(); - } - - @Test - void testClientManagerIsNotConnectedAndClosedInHandler() throws Exception { - // given - var clientManager = mock(Mqttv3ClientManager.class); - when(clientManager.getConnectionInfo()).thenReturn(new MqttConnectOptions()); - var client = mock(MqttAsyncClient.class); - given(clientManager.getClient()).willReturn(client); - - var deliveryToken = mock(MqttDeliveryToken.class); - given(client.publish(anyString(), any(), any(), any())).willReturn(deliveryToken); - - var handler = new MqttPahoMessageHandler(clientManager); - handler.setDefaultTopic("mqtt-foo"); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - handler.start(); - - // when - handler.handleMessage(new GenericMessage<>("Hello, world!")); - handler.stop(); - - // then - verify(client, never()).connect(any(MqttConnectOptions.class)); - verify(client).publish(anyString(), any(), any(), any()); - verify(client, never()).disconnect(); - verify(client, never()).disconnect(anyLong()); - verify(client, never()).close(); - } - - @Test - void testClientManagerIsNotConnectedAndClosedInAdapter() throws Exception { - // given - var clientManager = mock(Mqttv3ClientManager.class); - when(clientManager.getConnectionInfo()).thenReturn(new MqttConnectOptions()); - var client = mock(MqttAsyncClient.class); - given(clientManager.getClient()).willReturn(client); - - var subscribeToken = mock(MqttToken.class); - given(subscribeToken.getGrantedQos()).willReturn(new int[] {2}); - given(client.subscribe(any(String[].class), any(int[].class), any())) - .willReturn(subscribeToken); - - var adapter = new MqttPahoMessageDrivenChannelAdapter(clientManager, "mqtt-foo"); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - - // when - adapter.start(); - adapter.connectComplete(false, null); - adapter.stop(); - - // then - verify(client, never()).connect(any(MqttConnectOptions.class)); - verify(client).subscribe(eq(new String[] {"mqtt-foo"}), any(int[].class), any()); - verify(client).unsubscribe(new String[] {"mqtt-foo"}); - verify(client, never()).disconnect(); - verify(client, never()).disconnect(anyLong()); - verify(client, never()).close(); - } - - @Test - public void testInboundOptionsApplied() throws Exception { - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setCleanSession(false); - connectOptions.setConnectionTimeout(23); - connectOptions.setKeepAliveInterval(45); - connectOptions.setPassword("pass".toCharArray()); - MemoryPersistence persistence = new MemoryPersistence(); - factory.setPersistence(persistence); - final SocketFactory socketFactory = SocketFactory.getDefault(); - connectOptions.setSocketFactory(socketFactory); - final Properties props = new Properties(); - connectOptions.setSSLProperties(props); - connectOptions.setUserName("user"); - connectOptions.setWill("foo", "bar".getBytes(), 2, true); - factory.setConnectionOptions(connectOptions); - - factory = spy(factory); - final IMqttAsyncClient client = mock(IMqttAsyncClient.class); - willReturn(client).given(factory).getAsyncClientInstance(anyString(), anyString()); - - final AtomicBoolean connectCalled = new AtomicBoolean(); - IMqttToken token = mock(IMqttToken.class); - willAnswer(invocation -> { - MqttConnectOptions options = invocation.getArgument(0); - assertThat(options.getConnectionTimeout()).isEqualTo(23); - assertThat(options.getKeepAliveInterval()).isEqualTo(45); - assertThat(new String(options.getPassword())).isEqualTo("pass"); - assertThat(options.getSocketFactory()).isSameAs(socketFactory); - assertThat(options.getSSLProperties()).isSameAs(props); - assertThat(options.getUserName()).isEqualTo("user"); - assertThat(options.getWillDestination()).isEqualTo("foo"); - assertThat(new String(options.getWillMessage().getPayload())).isEqualTo("bar"); - assertThat(options.getWillMessage().getQos()).isEqualTo(2); - connectCalled.set(true); - return token; - }).given(client).connect(any(MqttConnectOptions.class)); - given(client.subscribe(any(String[].class), any(int[].class), any())).willReturn(token); - given(token.getGrantedQos()).willReturn(new int[] {2}); - - final AtomicReference callback = new AtomicReference<>(); - willAnswer(invocation -> { - callback.set(invocation.getArgument(0)); - return null; - }).given(client).setCallback(any(MqttCallbackExtended.class)); - - given(client.isConnected()).willReturn(true); - - MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("foo", "bar", factory, - "baz", "fix"); - adapter.setManualAcks(true); - QueueChannel outputChannel = new QueueChannel(); - adapter.setOutputChannel(outputChannel); - QueueChannel errorChannel = new QueueChannel(); - adapter.setErrorChannel(errorChannel); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - ApplicationEventPublisher applicationEventPublisher = mock(ApplicationEventPublisher.class); - final BlockingQueue events = new LinkedBlockingQueue<>(); - willAnswer(invocation -> { - events.add(invocation.getArgument(0)); - return null; - }).given(applicationEventPublisher).publishEvent(any(MqttIntegrationEvent.class)); - adapter.setApplicationEventPublisher(applicationEventPublisher); - adapter.afterPropertiesSet(); - adapter.start(); - adapter.connectComplete(false, null); - - verify(client, times(1)).connect(any(MqttConnectOptions.class)); - assertThat(connectCalled.get()).isTrue(); - - MqttMessage message = new MqttMessage("qux".getBytes()); - callback.get().messageArrived("baz", message); - Message outMessage = outputChannel.receive(0); - assertThat(outMessage).isNotNull(); - assertThat(outMessage.getPayload()).isEqualTo("qux"); - - StaticMessageHeaderAccessor.getAcknowledgment(outMessage).acknowledge(); - verify(client).setManualAcks(true); - verify(client) - .messageArrivedComplete(MqttHeaderAccessor.id(outMessage), MqttHeaderAccessor.receivedQos(outMessage)); - - MqttIntegrationEvent event = events.poll(10, TimeUnit.SECONDS); - assertThat(event).isInstanceOf(MqttSubscribedEvent.class); - assertThat(((MqttSubscribedEvent) event).getMessage()).isEqualTo("Connected and subscribed to [baz, fix]"); - - adapter.setConverter(new MqttMessageConverter() { - - @Override - public AbstractIntegrationMessageBuilder toMessageBuilder(String topic, MqttMessage mqttMessage) { - return null; - } - - @Override - public Object fromMessage(Message message, Class targetClass) { - return null; - } - - @Override - public Message toMessage(Object payload, MessageHeaders headers) { - return null; - } - - }); - - callback.get().messageArrived("baz", message); - - ErrorMessage errorMessage = (ErrorMessage) errorChannel.receive(0); - assertThat(errorMessage).isNotNull() - .extracting(Message::getPayload) - .isInstanceOf(IllegalStateException.class); - IllegalStateException exception = (IllegalStateException) errorMessage.getPayload(); - assertThat(exception).hasMessage("'MqttMessageConverter' returned 'null'"); - assertThat(errorMessage.getOriginalMessage().getPayload()).isSameAs(message); - } - - @Test - public void testStopActionDefault() throws Exception { - final IMqttAsyncClient client = mock(IMqttAsyncClient.class); - MqttPahoMessageDrivenChannelAdapter adapter = buildAdapterIn(client, null); - - adapter.start(); - adapter.connectComplete(false, null); - adapter.stop(); - verifyUnsubscribe(client); - } - - @Test - public void testStopActionDefaultNotClean() throws Exception { - final IMqttAsyncClient client = mock(IMqttAsyncClient.class); - MqttPahoMessageDrivenChannelAdapter adapter = buildAdapterIn(client, false); - - adapter.start(); - adapter.connectComplete(false, null); - adapter.stop(); - verifyNotUnsubscribe(client); - } - - @SuppressWarnings("unchecked") - @Test - public void testCustomExpressions() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); - MqttPahoMessageHandler handler = ctx.getBean("handler", MqttPahoMessageHandler.class); - GenericMessage message = new GenericMessage<>("foo"); - handler.setApplicationContext(ctx); - assertThat(TestUtils.getPropertyValue(handler, "topicProcessor", MessageProcessor.class) - .processMessage(message)).isEqualTo("fooTopic"); - assertThat(TestUtils.getPropertyValue(handler, "converter.qosProcessor", MessageProcessor.class) - .processMessage(message)).isEqualTo(1); - assertThat(TestUtils.getPropertyValue(handler, "converter.retainedProcessor", MessageProcessor.class) - .processMessage(message)).isEqualTo(Boolean.TRUE); - - handler = ctx.getBean("handlerWithNullExpressions", MqttPahoMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "converter", DefaultPahoMessageConverter.class) - .fromMessage(message, null).getQos()).isEqualTo(1); - assertThat(TestUtils.getPropertyValue(handler, "converter", DefaultPahoMessageConverter.class) - .fromMessage(message, null).isRetained()).isEqualTo(Boolean.TRUE); - ctx.close(); - } - - @Test - public void testSubscribeFailure() throws Exception { - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setCleanSession(false); - connectOptions.setConnectionTimeout(23); - connectOptions.setKeepAliveInterval(45); - connectOptions.setPassword("pass".toCharArray()); - MemoryPersistence persistence = new MemoryPersistence(); - factory.setPersistence(persistence); - final SocketFactory socketFactory = SocketFactory.getDefault(); - connectOptions.setSocketFactory(socketFactory); - final Properties props = new Properties(); - connectOptions.setSSLProperties(props); - connectOptions.setUserName("user"); - connectOptions.setWill("foo", "bar".getBytes(), 2, true); - - factory = spy(factory); - final MqttAsyncClient client = mock(MqttAsyncClient.class); - willReturn(client).given(factory).getAsyncClientInstance(anyString(), anyString()); - given(client.isConnected()).willReturn(true); - willReturn(alwaysComplete).given(client).connect(any(MqttConnectOptions.class)); - - IMqttToken token = mock(IMqttToken.class); - given(token.getGrantedQos()).willReturn(new int[] {0x80}); - willReturn(token).given(client).subscribe(any(String[].class), any(int[].class), any()); - - MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("foo", "bar", factory, - "baz", "fix"); - AtomicReference method = new AtomicReference<>(); - ReflectionUtils.doWithMethods(MqttPahoMessageDrivenChannelAdapter.class, m -> { - m.setAccessible(true); - method.set(m); - }, m -> m.getName().equals("connect")); - assertThat(method.get()).isNotNull(); - method.get().invoke(adapter); - ReflectionUtils.doWithMethods(MqttPahoMessageDrivenChannelAdapter.class, m -> { - m.setAccessible(true); - method.set(m); - }, m -> m.getName().equals("subscribe")); - assertThat(method.get()).isNotNull(); - ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); - adapter.setApplicationEventPublisher(eventPublisher); - method.get().invoke(adapter); - verify(eventPublisher).publishEvent(any(MqttConnectionFailedEvent.class)); - } - - @Test - public void testDifferentQos() throws Exception { - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setCleanSession(false); - connectOptions.setConnectionTimeout(23); - connectOptions.setKeepAliveInterval(45); - connectOptions.setPassword("pass".toCharArray()); - MemoryPersistence persistence = new MemoryPersistence(); - factory.setPersistence(persistence); - final SocketFactory socketFactory = SocketFactory.getDefault(); - connectOptions.setSocketFactory(socketFactory); - final Properties props = new Properties(); - connectOptions.setSSLProperties(props); - connectOptions.setUserName("user"); - connectOptions.setWill("foo", "bar".getBytes(), 2, true); - - factory = spy(factory); - final MqttAsyncClient client = mock(MqttAsyncClient.class); - willReturn(client).given(factory).getAsyncClientInstance(anyString(), anyString()); - given(client.isConnected()).willReturn(true); - willReturn(alwaysComplete).given(client).connect(any(MqttConnectOptions.class)); - - IMqttToken token = mock(IMqttToken.class); - given(token.getGrantedQos()).willReturn(new int[] {2, 0}); - willReturn(token).given(client).subscribe(any(String[].class), any(int[].class), any()); - - MqttPahoMessageDrivenChannelAdapter adapter = - new MqttPahoMessageDrivenChannelAdapter("tcp://mqtt.host", "bar", factory, "baz", "fix"); - adapter.setApplicationEventPublisher(mock(ApplicationEventPublisher.class)); - AtomicReference method = new AtomicReference<>(); - ReflectionUtils.doWithMethods(MqttPahoMessageDrivenChannelAdapter.class, m -> { - m.setAccessible(true); - method.set(m); - }, m -> m.getName().equals("connect")); - assertThat(method.get()).isNotNull(); - method.get().invoke(adapter); - ReflectionUtils.doWithMethods(MqttPahoMessageDrivenChannelAdapter.class, m -> { - m.setAccessible(true); - method.set(m); - }, m -> m.getName().equals("subscribe")); - assertThat(method.get()).isNotNull(); - LogAccessor logger = spy(TestUtils.getPropertyValue(adapter, "logger", LogAccessor.class)); - new DirectFieldAccessor(adapter).setPropertyValue("logger", logger); - given(logger.isWarnEnabled()).willReturn(true); - method.get().invoke(adapter); - verify(logger, atLeastOnce()) - .warn(ArgumentMatchers.>argThat(logMessage -> - logMessage.get() - .equals("Granted QOS different to Requested QOS; topics: [baz, fix] " + - "requested: [1, 1] granted: [2, 0]"))); - - new DirectFieldAccessor(adapter).setPropertyValue("running", Boolean.TRUE); - adapter.stop(); - verify(client).disconnectForcibly(30_000L, 5_000L); - } - - @Test - public void emptyTopicNotAllowed() { - assertThatIllegalArgumentException() - .isThrownBy(() -> - new MqttPahoMessageDrivenChannelAdapter("client_id", mock(MqttPahoClientFactory.class), "")) - .withMessage("The topic to subscribe cannot be empty string"); - - var adapter = new MqttPahoMessageDrivenChannelAdapter("client_id", mock(MqttPahoClientFactory.class), "topic1"); - assertThatIllegalArgumentException() - .isThrownBy(() -> adapter.addTopic("")) - .withMessage("The topic to subscribe cannot be empty string"); - } - - private MqttPahoMessageDrivenChannelAdapter buildAdapterIn(final IMqttAsyncClient client, Boolean cleanSession) - throws MqttException { - - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory() { - - @Override - public IMqttAsyncClient getAsyncClientInstance(String uri, String clientId) { - return client; - } - - }; - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setServerURIs(new String[] {"tcp://localhost:1883"}); - if (cleanSession != null) { - connectOptions.setCleanSession(cleanSession); - } - factory.setConnectionOptions(connectOptions); - given(client.isConnected()).willReturn(true); - IMqttToken token = mock(IMqttToken.class); - given(client.connect(any(MqttConnectOptions.class))).willReturn(token); - given(client.subscribe(any(String[].class), any(int[].class), any())).willReturn(token); - given(token.getGrantedQos()).willReturn(new int[] {2}); - MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("client", factory, "foo"); - adapter.setApplicationEventPublisher(mock(ApplicationEventPublisher.class)); - adapter.setOutputChannel(new NullChannel()); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - return adapter; - } - - private MqttPahoMessageHandler buildAdapterOut(final IMqttAsyncClient client) { - DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory() { - - @Override - public IMqttAsyncClient getAsyncClientInstance(String uri, String clientId) { - return client; - } - - }; - MqttConnectOptions connectOptions = new MqttConnectOptions(); - connectOptions.setServerURIs(new String[] {"tcp://localhost:1883"}); - factory.setConnectionOptions(connectOptions); - MqttPahoMessageHandler adapter = new MqttPahoMessageHandler("client", factory); - adapter.setDefaultTopic("foo"); - adapter.setApplicationEventPublisher(mock(ApplicationEventPublisher.class)); - adapter.setBeanFactory(TEST_INTEGRATION_CONTEXT); - adapter.afterPropertiesSet(); - return adapter; - } - - private void verifyUnsubscribe(IMqttAsyncClient client) throws Exception { - verify(client).connect(any(MqttConnectOptions.class)); - verify(client).subscribe(any(String[].class), any(int[].class), any()); - verify(client).unsubscribe(any(String[].class)); - verify(client).disconnectForcibly(anyLong(), anyLong()); - } - - private void verifyNotUnsubscribe(IMqttAsyncClient client) throws Exception { - verify(client).connect(any(MqttConnectOptions.class)); - verify(client).subscribe(any(String[].class), any(int[].class), any()); - verify(client, never()).unsubscribe(any(String[].class)); - verify(client).disconnectForcibly(anyLong(), anyLong()); - } - - @Configuration - public static class Config { - - @Bean - public MqttPahoMessageHandler handler() { - MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "bar"); - handler.setTopicExpressionString("@topic"); - handler.setQosExpressionString("@qos"); - handler.setRetainedExpressionString("@retained"); - return handler; - } - - @Bean - public String topic() { - return "fooTopic"; - } - - @Bean - public Integer qos() { - return 1; - } - - @Bean - public Boolean retained() { - return true; - } - - @Bean - public MqttPahoMessageHandler handlerWithNullExpressions() { - MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "bar"); - handler.setDefaultQos(1); - handler.setQosExpressionString("null"); - handler.setDefaultRetained(true); - handler.setRetainedExpressionString("null"); - return handler; - } - - @Bean - public StandardEvaluationContext integrationEvaluationContext(ApplicationContext applicationContext) { - StandardEvaluationContext integrationEvaluationContext = new StandardEvaluationContext(); - integrationEvaluationContext.addPropertyAccessor(new MapAccessor()); - integrationEvaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext)); - return integrationEvaluationContext; - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MqttDslTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MqttDslTests.java deleted file mode 100644 index d7e7f571ed2..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/MqttDslTests.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2018-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.util.Set; - -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.jmx.support.MBeanServerFactoryBean; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.1.2 - */ -@SpringJUnitConfig -@DirtiesContext -public class MqttDslTests implements MosquittoContainerTest, TestApplicationContextAware { - - @Autowired - @Qualifier("mqttOutFlow.input") - private MessageChannel mqttOutFlowInput; - - @Autowired - private PollableChannel fromMqttChannel; - - @Autowired - private MBeanServer server; - - @Test - public void testMqttChannelAdaptersAndJmx() throws MalformedObjectNameException { - Set mbeanNames = this.server.queryNames( - new ObjectName("org.springframework.integration:type=ManagedEndpoint,*"), null); - - assertThat(mbeanNames.size()).isEqualTo(1); - ObjectName objectName = mbeanNames.iterator().next(); - assertThat(objectName.toString()).contains("name=\"mqttInFlow.mqtt:inbound-channel-adapter#0\""); - - String testPayload = "foo"; - - this.mqttOutFlowInput.send( - MessageBuilder.withPayload(testPayload) - .setHeader(MqttHeaders.TOPIC, "jmxTests") - .build()); - - Message receive = this.fromMqttChannel.receive(10_000); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(testPayload); - } - - @Configuration - @EnableIntegration - @EnableIntegrationMBeanExport(server = "mbeanServer") - public static class Config { - - @Bean - public static MBeanServerFactoryBean mbeanServer() { - return new MBeanServerFactoryBean(); - } - - @Bean - public DefaultMqttPahoClientFactory pahoClientFactory() { - DefaultMqttPahoClientFactory pahoClientFactory = new DefaultMqttPahoClientFactory(); - MqttConnectOptions connectionOptions = new MqttConnectOptions(); - connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()}); - pahoClientFactory.setConnectionOptions(connectionOptions); - return pahoClientFactory; - } - - @Bean - public IntegrationFlow mqttOutFlow() { - return f -> f.handle(new MqttPahoMessageHandler("jmxTestOut", pahoClientFactory())); - } - - @Bean - public IntegrationFlow mqttInFlow() { - return IntegrationFlow.from( - new MqttPahoMessageDrivenChannelAdapter("jmxTestIn", - pahoClientFactory(), "jmxTests")) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5AdapterTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5AdapterTests.java deleted file mode 100644 index 58e8dccb62a..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5AdapterTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import org.eclipse.paho.mqttv5.client.IMqttAsyncClient; -import org.eclipse.paho.mqttv5.client.IMqttMessageListener; -import org.eclipse.paho.mqttv5.client.IMqttToken; -import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; -import org.eclipse.paho.mqttv5.common.MqttException; -import org.eclipse.paho.mqttv5.common.MqttSubscription; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.mqtt.inbound.Mqttv5PahoMessageDrivenChannelAdapter; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * @author Matthias Thoma - * - * @since 5.5.16 - * - */ -public class Mqttv5AdapterTests { - - @Test - public void testStop() throws Exception { - final IMqttAsyncClient client = mock(IMqttAsyncClient.class); - Mqttv5PahoMessageDrivenChannelAdapter adapter = buildAdapterIn(client, true); - - adapter.start(); - adapter.connectComplete(false, null); - adapter.stop(); - - verify(client).connect(any(MqttConnectionOptions.class)); - verify(client).subscribe(any(MqttSubscription[].class), any(), any(), any(IMqttMessageListener.class), any()); - verify(client).unsubscribe(any(String[].class)); - } - - @Test - public void testStopNotClean() throws Exception { - final IMqttAsyncClient client = mock(IMqttAsyncClient.class); - Mqttv5PahoMessageDrivenChannelAdapter adapter = buildAdapterIn(client, false); - - adapter.start(); - adapter.connectComplete(false, null); - adapter.stop(); - - verify(client).connect(any(MqttConnectionOptions.class)); - verify(client).subscribe(any(MqttSubscription[].class), any(), any(), any(IMqttMessageListener.class), any()); - verify(client, never()).unsubscribe(any(String[].class)); - } - - private static Mqttv5PahoMessageDrivenChannelAdapter buildAdapterIn(final IMqttAsyncClient client, - boolean cleanStart) throws MqttException { - - MqttConnectionOptions connectionOptions = new MqttConnectionOptions(); - connectionOptions.setServerURIs(new String[] {"tcp://localhost:1883"}); - connectionOptions.setCleanStart(cleanStart); - - given(client.isConnected()).willReturn(true); - IMqttToken token = mock(IMqttToken.class); - given(client.disconnect()).willReturn(token); - given(client.connect(any(MqttConnectionOptions.class))).willReturn(token); - given(client.subscribe(any(MqttSubscription[].class), any(), any(), any(IMqttMessageListener.class), any())) - .willReturn(token); - given(client.unsubscribe(any(String[].class))).willReturn(token); - Mqttv5PahoMessageDrivenChannelAdapter adapter = - new Mqttv5PahoMessageDrivenChannelAdapter(connectionOptions, "client", "foo"); - ReflectionTestUtils.setField(adapter, "mqttClient", client); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.setApplicationEventPublisher(mock(ApplicationEventPublisher.class)); - adapter.setOutputChannel(new NullChannel()); - adapter.afterPropertiesSet(); - return adapter; - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackAutomaticReconnectTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackAutomaticReconnectTests.java deleted file mode 100644 index 2079fd0f98a..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackAutomaticReconnectTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.net.UnknownHostException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; -import org.eclipse.paho.mqttv5.client.MqttConnectionOptionsBuilder; -import org.eclipse.paho.mqttv5.common.MqttException; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.EventListener; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.inbound.Mqttv5PahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.Mqttv5PahoMessageHandler; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Lucas Bowler - * @author Artem Bilan - * - * @since 5.5.13 - */ -@SpringJUnitConfig -@DirtiesContext -public class Mqttv5BackToBackAutomaticReconnectTests implements MosquittoContainerTest { - - @Autowired - @Qualifier("mqttOutFlow.input") - private MessageChannel mqttOutFlowInput; - - @Autowired - private PollableChannel fromMqttChannel; - - @Autowired - private MqttConnectionOptions connectionOptions; - - @Autowired - Config config; - - @Test - public void testReconnectionWhenFirstConnectionFails() throws InterruptedException { - Message testMessage = - MessageBuilder.withPayload("testPayload") - .setHeader(MqttHeaders.TOPIC, "siTest") - .build(); - - assertThatExceptionOfType(MessageHandlingException.class) - .isThrownBy(() -> this.mqttOutFlowInput.send(testMessage)) - .withCauseExactlyInstanceOf(MqttException.class) - .withRootCauseExactlyInstanceOf(UnknownHostException.class); - - connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()}); - - assertThat(this.config.subscribeLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - this.mqttOutFlowInput.send(testMessage); - - Message receive = this.fromMqttChannel.receive(10_000); - - assertThat(receive).isNotNull(); - } - - @Configuration - @EnableIntegration - public static class Config { - - CountDownLatch subscribeLatch = new CountDownLatch(1); - - @Bean - public MqttConnectionOptions mqttConnectOptions() { - return new MqttConnectionOptionsBuilder() - .serverURI("wss://badMqttUrl") - .automaticReconnect(true) - .connectionTimeout(1) - .build(); - } - - @Bean - public IntegrationFlow mqttOutFlow() { - Mqttv5PahoMessageHandler messageHandler = - new Mqttv5PahoMessageHandler(mqttConnectOptions(), "mqttv5SIout"); - - return f -> f.handle(messageHandler); - } - - @Bean - public IntegrationFlow mqttInFlow() { - Mqttv5PahoMessageDrivenChannelAdapter messageProducer = - new Mqttv5PahoMessageDrivenChannelAdapter(mqttConnectOptions(), "mqttv5SIin", "siTest"); - messageProducer.setPayloadType(String.class); - - return IntegrationFlow.from(messageProducer) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - @EventListener(MqttSubscribedEvent.class) - void mqttEvents() { - this.subscribeLatch.countDown(); - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java deleted file mode 100644 index 4d634a772fd..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.paho.mqttv5.common.MqttSubscription; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.EventListener; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.acks.SimpleAcknowledgment; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.mqtt.event.MqttIntegrationEvent; -import org.springframework.integration.mqtt.event.MqttMessageDeliveredEvent; -import org.springframework.integration.mqtt.event.MqttMessageSentEvent; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.inbound.Mqttv5PahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.Mqttv5PahoMessageHandler; -import org.springframework.integration.mqtt.support.MqttHeaderMapper; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.converter.AbstractMessageConverter; -import org.springframework.messaging.converter.SmartMessageConverter; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Mikhail Polivakha - * @author Glenn Renfro - * - * @since 5.5.5 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class Mqttv5BackToBackTests implements MosquittoContainerTest { - - private static final long QUIESCENT_TIMEOUT = 1; - - private static final long DISCONNECT_COMPLETION_TIMEOUT = 1L; - - @Autowired - @Qualifier("mqttOutFlow.input") - private MessageChannel mqttOutFlowInput; - - @Autowired - private PollableChannel fromMqttChannel; - - @Autowired - private Config config; - - @Autowired - private Mqttv5PahoMessageDrivenChannelAdapter mqttv5MessageDrivenChannelAdapter; - - @Test //GH-3732 - public void testNoNpeIsNotThrownInCaseDoInitIsNotInvokedBeforeTopicAddition() { - Mqttv5PahoMessageDrivenChannelAdapter channelAdapter = - new Mqttv5PahoMessageDrivenChannelAdapter("tcp://mock-url.com:8091", "mock-client-id", "123"); - Assertions.assertDoesNotThrow(() -> channelAdapter.addTopic("abc", 1)); - } - - @Test //GH-3732 - public void testNoNpeIsNotThrownInCaseDoInitIsNotInvokedBeforeTopicRemoval() { - Mqttv5PahoMessageDrivenChannelAdapter channelAdapter = - new Mqttv5PahoMessageDrivenChannelAdapter("tcp://mock-url.com:8091", "mock-client-id", "123"); - Assertions.assertDoesNotThrow(() -> channelAdapter.removeTopic("abc")); - } - - @Test - public void testSimpleMqttv5Interaction() { - String testPayload = "datakey"; - - this.mqttOutFlowInput.send( - MessageBuilder.withPayload(testPayload) - .setHeader(MqttHeaders.TOPIC, "siTest") - .setHeader("foo", "bar") - .setHeader(MessageHeaders.CONTENT_TYPE, "text/plain") - .build()); - - Message receive = this.fromMqttChannel.receive(10_000); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(testPayload); - assertThat(receive.getHeaders()) - .containsEntry("foo", "bar") - .containsEntry(MessageHeaders.CONTENT_TYPE, "text/plain") - .containsKey(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK); - - receive.getHeaders() - .get(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, SimpleAcknowledgment.class) - .acknowledge(); - - assertThat(this.config.events) - .isNotEmpty() - .hasAtLeastOneElementOfType(MqttMessageSentEvent.class) - .hasAtLeastOneElementOfType(MqttMessageDeliveredEvent.class) - .hasAtLeastOneElementOfType(MqttSubscribedEvent.class); - - this.mqttv5MessageDrivenChannelAdapter.addTopic("anotherTopic"); - - testPayload = "another payload"; - - this.mqttOutFlowInput.send( - MessageBuilder.withPayload(testPayload) - .setHeader(MqttHeaders.TOPIC, "anotherTopic") - .build()); - - receive = this.fromMqttChannel.receive(10_000); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(testPayload); - } - - @Test - public void testSharedTopicMqttv5Interaction() { - this.mqttv5MessageDrivenChannelAdapter.addTopic("$share/group/testTopic"); - - String testPayload = "shared topic payload"; - this.mqttOutFlowInput.send( - MessageBuilder.withPayload(testPayload) - .setHeader(MqttHeaders.TOPIC, "testTopic") - .build()); - - Message receive = this.fromMqttChannel.receive(10_000); - - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(testPayload); - } - - @Configuration - @EnableIntegration - public static class Config { - - List events = new ArrayList<>(); - - @EventListener - void mqttEvents(MqttIntegrationEvent event) { - this.events.add(event); - } - - @Bean - public SmartMessageConverter mqttStringToBytesConverter() { - return new AbstractMessageConverter() { - - @Override - protected boolean supports(Class clazz) { - return true; - } - - @Override - protected Object convertFromInternal(Message message, Class targetClass, - Object conversionHint) { - - return message.getPayload().toString().getBytes(StandardCharsets.UTF_8); - } - - @Override - protected Object convertToInternal(Object payload, MessageHeaders headers, - Object conversionHint) { - - return new String((byte[]) payload); - } - - }; - } - - @Bean - public IntegrationFlow mqttOutFlow() { - Mqttv5PahoMessageHandler messageHandler = - new Mqttv5PahoMessageHandler(MosquittoContainerTest.mqttUrl(), "mqttv5SIout"); - MqttHeaderMapper mqttHeaderMapper = new MqttHeaderMapper(); - mqttHeaderMapper.setOutboundHeaderNames("foo", MessageHeaders.CONTENT_TYPE); - messageHandler.setHeaderMapper(mqttHeaderMapper); - messageHandler.setAsync(true); - messageHandler.setAsyncEvents(true); - messageHandler.setConverter(mqttStringToBytesConverter()); - - return f -> f.handle(messageHandler); - } - - @Bean - public IntegrationFlow mqttInFlow() { - MqttSubscription mqttSubscription = new MqttSubscription("siTest", 2); - mqttSubscription.setNoLocal(true); - mqttSubscription.setRetainAsPublished(true); - Mqttv5PahoMessageDrivenChannelAdapter messageProducer = - new Mqttv5PahoMessageDrivenChannelAdapter(MosquittoContainerTest.mqttUrl(), "mqttv5SIin", - mqttSubscription); - messageProducer.setPayloadType(String.class); - messageProducer.setQuiescentTimeout(QUIESCENT_TIMEOUT); - messageProducer.setDisconnectCompletionTimeout(DISCONNECT_COMPLETION_TIMEOUT); - messageProducer.setMessageConverter(mqttStringToBytesConverter()); - messageProducer.setManualAcks(true); - - return IntegrationFlow.from(messageProducer) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/ResubscribeAfterAutomaticReconnectTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/ResubscribeAfterAutomaticReconnectTests.java deleted file mode 100644 index ded7d1492fb..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/ResubscribeAfterAutomaticReconnectTests.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2023-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt; - -import java.time.Duration; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; -import org.eclipse.paho.mqttv5.client.MqttConnectionOptionsBuilder; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.EventListener; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.mqtt.event.MqttSubscribedEvent; -import org.springframework.integration.mqtt.inbound.Mqttv5PahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.outbound.Mqttv5PahoMessageHandler; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -/** - * @author Artem Bilan - * - * @since 6.0.3 - */ -@SpringJUnitConfig -@DirtiesContext -public class ResubscribeAfterAutomaticReconnectTests implements MosquittoContainerTest { - - @Autowired - @Qualifier("mqttOutFlow.input") - private MessageChannel mqttOutFlowInput; - - @Autowired - private PollableChannel fromMqttChannel; - - @Autowired - private MqttConnectionOptions connectionOptions; - - @Autowired - Mqttv5PahoMessageDrivenChannelAdapter pahoMessageDrivenChannelAdapter; - - @Autowired - Config config; - - @Test - void messageReceivedAfterResubscriptionOnLostConnection() throws InterruptedException { - GenericMessage testMessage = new GenericMessage<>("test"); - - assertThat(this.config.subscribeFirstLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - this.mqttOutFlowInput.send(testMessage); - assertThat(this.fromMqttChannel.receive(10_000)).isNotNull(); - - MOSQUITTO_CONTAINER.stop(); - MOSQUITTO_CONTAINER.start(); - connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()}); - - assertThat(this.config.subscribeSecondLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> this.mqttOutFlowInput.send(testMessage)); - assertThat(this.fromMqttChannel.receive(10_000)).isNotNull(); - - // Re-subscription on channel adapter restart with cleanStart - this.pahoMessageDrivenChannelAdapter.stop(); - this.pahoMessageDrivenChannelAdapter.start(); - - assertThat(this.config.subscribeThirdLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - this.mqttOutFlowInput.send(testMessage); - assertThat(this.fromMqttChannel.receive(10_000)).isNotNull(); - } - - @Configuration - @EnableIntegration - public static class Config { - - CountDownLatch subscribeFirstLatch = new CountDownLatch(1); - - CountDownLatch subscribeSecondLatch = new CountDownLatch(2); - - CountDownLatch subscribeThirdLatch = new CountDownLatch(3); - - @Bean - public MqttConnectionOptions mqttConnectOptions() { - return new MqttConnectionOptionsBuilder() - .serverURI(MosquittoContainerTest.mqttUrl()) - .automaticReconnect(true) - .cleanStart(true) - .build(); - } - - @Bean - public IntegrationFlow mqttOutFlow(MqttConnectionOptions mqttConnectOptions) { - Mqttv5PahoMessageHandler messageHandler = - new Mqttv5PahoMessageHandler(mqttConnectOptions, "mqttv5SIout"); - messageHandler.setDefaultTopic("siTest"); - return f -> f.handle(messageHandler); - } - - @Bean - public IntegrationFlow mqttInFlow(MqttConnectionOptions mqttConnectOptions) { - Mqttv5PahoMessageDrivenChannelAdapter messageProducer = - new Mqttv5PahoMessageDrivenChannelAdapter(mqttConnectOptions, "mqttInClient", "siTest"); - return IntegrationFlow.from(messageProducer) - .channel(c -> c.queue("fromMqttChannel")) - .get(); - } - - @EventListener(MqttSubscribedEvent.class) - public void mqttEvents() { - this.subscribeFirstLatch.countDown(); - this.subscribeSecondLatch.countDown(); - this.subscribeThirdLatch.countDown(); - } - - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParserTests-context.xml b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParserTests-context.xml deleted file mode 100644 index e0e92a480e1..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParserTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParserTests.java deleted file mode 100644 index 8ab6429f50c..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttMessageDrivenChannelAdapterParserTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.config.xml; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class MqttMessageDrivenChannelAdapterParserTests { - - @Autowired - private MqttPahoMessageDrivenChannelAdapter noTopicsAdapter; - - @Autowired - private MqttPahoMessageDrivenChannelAdapter noTopicsAdapterDefaultCF; - - @Autowired - private MqttPahoMessageDrivenChannelAdapter oneTopicAdapter; - - @Autowired - private MqttPahoMessageDrivenChannelAdapter twoTopicsAdapter; - - @Autowired - private MqttPahoMessageDrivenChannelAdapter twoTopicsSingleQosAdapter; - - @Autowired - private MessageChannel out; - - @Autowired - private MqttMessageConverter converter; - - @Autowired - private DefaultMqttPahoClientFactory clientFactory; - - @Autowired - private MessageChannel errors; - - @Test - @SuppressWarnings("unchecked") - public void testNoTopics() { - assertThat(TestUtils.getPropertyValue(noTopicsAdapter, "url")).isEqualTo("tcp://localhost:1883"); - assertThat(TestUtils.getPropertyValue(noTopicsAdapter, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(noTopicsAdapter, "clientId")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(noTopicsAdapter, "topics", Map.class)).hasSize(0); - assertThat(TestUtils.getPropertyValue(noTopicsAdapter, "outputChannel")).isSameAs(out); - assertThat(TestUtils.getPropertyValue(noTopicsAdapter, "clientFactory")).isSameAs(clientFactory); - assertThat(TestUtils.getPropertyValue(this.noTopicsAdapter, "manualAcks", Boolean.class)).isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - public void testNoTopicsDefaultCF() { - assertThat(TestUtils.getPropertyValue(noTopicsAdapterDefaultCF, "url")).isEqualTo("tcp://localhost:1883"); - assertThat(TestUtils.getPropertyValue(noTopicsAdapterDefaultCF, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(noTopicsAdapterDefaultCF, "clientId")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(noTopicsAdapterDefaultCF, "topics", Map.class)).hasSize(0); - assertThat(TestUtils.getPropertyValue(noTopicsAdapterDefaultCF, "outputChannel")).isSameAs(out); - assertThat(TestUtils.getPropertyValue(this.noTopicsAdapterDefaultCF, "manualAcks", Boolean.class)).isFalse(); - } - - @Test - @SuppressWarnings("unchecked") - public void testOneTopic() { - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "url")).isEqualTo("tcp://localhost:1883"); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "phase")).isEqualTo(25); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "clientId")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "topics", Map.class)).containsEntry("bar", 1); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "converter")).isSameAs(converter); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "messagingTemplate.sendTimeout")).isEqualTo(123L); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "outputChannel")).isSameAs(out); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "clientFactory")).isSameAs(clientFactory); - assertThat(TestUtils.getPropertyValue(oneTopicAdapter, "errorChannel")).isSameAs(errors); - } - - @Test - @SuppressWarnings("unchecked") - public void testTwoTopics() { - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "url")).isEqualTo("tcp://localhost:1883"); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "phase")).isEqualTo(25); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "clientId")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "topics", Map.class)) - .containsEntry("bar", 0) - .containsEntry("baz", 2); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "converter")).isSameAs(converter); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "messagingTemplate.sendTimeout")).isEqualTo(123L); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "outputChannel")).isSameAs(out); - assertThat(TestUtils.getPropertyValue(twoTopicsAdapter, "clientFactory")).isSameAs(clientFactory); - } - - @Test - @SuppressWarnings("unchecked") - public void testTwoTopicsSingleQos() { - assertThat(TestUtils.getPropertyValue(twoTopicsSingleQosAdapter, "topics", Map.class)) - .containsEntry("bar", 0) - .containsEntry("baz", 0); - } - -} diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests-context.xml b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index d064d5299f5..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java deleted file mode 100644 index e004d50dc4e..00000000000 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.mqtt.config.xml; - -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; -import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; -import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; -import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; -import org.springframework.integration.mqtt.support.MqttMessageConverter; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class MqttOutboundChannelAdapterParserTests { - - @Autowired - @Qualifier("withConverter") - private EventDrivenConsumer withConverterEndpoint; - - @Autowired - @Qualifier("withConverter.handler") - private MessageHandler withConverterHandler; - - @Autowired - @Qualifier("withDefaultConverter.handler") - private MqttPahoMessageHandler withDefaultConverterHandler; - - @Autowired - private MqttMessageConverter converter; - - @Autowired - private DefaultMqttPahoClientFactory clientFactory; - - @SuppressWarnings("unchecked") - @Test - public void testWithConverter() throws Exception { - assertThat(TestUtils.getPropertyValue(withConverterHandler, "url")).isEqualTo("tcp://localhost:1883"); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "clientId")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "defaultTopic")).isEqualTo("bar"); - GenericMessage message = new GenericMessage<>("foo"); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "topicProcessor", MessageProcessor.class) - .processMessage(message)).isEqualTo("bar"); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "qosProcessor", MessageProcessor.class) - .processMessage(message)).isEqualTo(2); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "retainedProcessor", MessageProcessor.class) - .processMessage(message)).isEqualTo(Boolean.TRUE); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "converter")).isSameAs(converter); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "clientFactory")).isSameAs(clientFactory); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "async", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(withConverterHandler, "asyncEvents", Boolean.class)).isFalse(); - - Object handler = TestUtils.getPropertyValue(this.withConverterEndpoint, "handler"); - - assertThat(AopUtils.isAopProxy(handler)).isTrue(); - - assertThat(this.withConverterHandler).isSameAs(((Advised) handler).getTargetSource().getTarget()); - - assertThat(((Advised) handler).getAdvisors()[0].getAdvice()).isInstanceOf(RequestHandlerRetryAdvice.class); - } - - @Test - public void testWithDefaultConverter() { - GenericMessage message = new GenericMessage<>("foo"); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "url")).isEqualTo("tcp://localhost:1883"); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "clientId")).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "defaultTopic")).isEqualTo("bar"); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "defaultQos")).isEqualTo(1); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "defaultRetained", Boolean.class)) - .isEqualTo(Boolean.TRUE); - DefaultPahoMessageConverter defaultConverter = TestUtils.getPropertyValue(withDefaultConverterHandler, - "converter", DefaultPahoMessageConverter.class); - assertThat(defaultConverter.fromMessage(message, null).getQos()).isEqualTo(1); - assertThat(defaultConverter.fromMessage(message, null).isRetained()).isTrue(); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "clientFactory")).isSameAs(clientFactory); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "async", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(withDefaultConverterHandler, "asyncEvents", Boolean.class)).isTrue(); - } - -} diff --git a/spring-integration-mqtt/src/test/resources/log4j2-test.xml b/spring-integration-mqtt/src/test/resources/log4j2-test.xml deleted file mode 100644 index f5fe82aa984..00000000000 --- a/spring-integration-mqtt/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbc.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbc.java deleted file mode 100644 index ed362c9ce8c..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbc.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.dsl; - -import java.util.function.Function; - -import org.springframework.data.r2dbc.core.R2dbcEntityOperations; -import org.springframework.data.r2dbc.core.StatementMapper; -import org.springframework.expression.Expression; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.r2dbc.inbound.R2dbcMessageSource; - -/** - * Java DSL Factory class for R2DBC components. - * - * @author Artem Bilan - * - * @since 5.4 - */ -public final class R2dbc { - - /** - * Create an instance of {@link R2dbcMessageSourceSpec} for the provided {@link R2dbcEntityOperations} - * and query string. - * @param r2dbcEntityOperations the {@link R2dbcEntityOperations} to use. - * @param query the query to execute. - * @return the spec. - */ - public static R2dbcMessageSourceSpec inboundChannelAdapter(R2dbcEntityOperations r2dbcEntityOperations, - String query) { - - return new R2dbcMessageSourceSpec(r2dbcEntityOperations, query); - } - - /** - * Create an instance of {@link R2dbcMessageSourceSpec} for the provided {@link R2dbcEntityOperations} - * and function to create a {@link StatementMapper.SelectSpec} instance. - * @param r2dbcEntityOperations the {@link R2dbcEntityOperations} to use. - * @param selectFunction the expression to evaluate a query for execution. - * @return the spec. - */ - public static R2dbcMessageSourceSpec inboundChannelAdapter(R2dbcEntityOperations r2dbcEntityOperations, - Function selectFunction) { - - return inboundChannelAdapter(r2dbcEntityOperations, new FunctionExpression<>(selectFunction)); - } - - /** - * Create an instance of {@link R2dbcMessageSourceSpec} for the provided {@link R2dbcEntityOperations} - * and SpEL expression for query. - * @param r2dbcEntityOperations the {@link R2dbcEntityOperations} to use. - * @param queryExpression the expression to evaluate a query for execution. - * @return the spec. - */ - public static R2dbcMessageSourceSpec inboundChannelAdapter(R2dbcEntityOperations r2dbcEntityOperations, - Expression queryExpression) { - - return new R2dbcMessageSourceSpec(r2dbcEntityOperations, queryExpression); - } - - /** - * Create an instance of {@link R2dbcMessageHandlerSpec} for the provided {@link R2dbcEntityOperations}. - * @param r2dbcEntityOperations the {@link R2dbcEntityOperations} to use. - * @return the spec. - */ - public static R2dbcMessageHandlerSpec outboundChannelAdapter(R2dbcEntityOperations r2dbcEntityOperations) { - return new R2dbcMessageHandlerSpec(r2dbcEntityOperations); - } - - private R2dbc() { - } - -} diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbcMessageHandlerSpec.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbcMessageHandlerSpec.java deleted file mode 100644 index c04d41f8a30..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbcMessageHandlerSpec.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.dsl; - -import java.util.Map; -import java.util.function.Function; - -import org.springframework.data.r2dbc.core.R2dbcEntityOperations; -import org.springframework.data.relational.core.query.Criteria; -import org.springframework.expression.Expression; -import org.springframework.integration.dsl.ReactiveMessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.r2dbc.outbound.R2dbcMessageHandler; -import org.springframework.messaging.Message; - -/** - * The {@link ReactiveMessageHandlerSpec} for the {@link R2dbcMessageHandler}. - * - * @author Artem Bilan - * - * @since 5.4 - */ -public class R2dbcMessageHandlerSpec extends ReactiveMessageHandlerSpec { - - protected R2dbcMessageHandlerSpec(R2dbcEntityOperations r2dbcEntityOperations) { - super(new R2dbcMessageHandler(r2dbcEntityOperations)); - } - - /** - * Set a {@link R2dbcMessageHandler.Type} for query to execute. - * @param type the {@link R2dbcMessageHandler.Type} to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec queryType(R2dbcMessageHandler.Type type) { - this.reactiveMessageHandler.setQueryType(type); - return this; - } - - /** - * Set a {@link Function} to evaluate a {@link R2dbcMessageHandler.Type} for query to execute against a request - * message. - * @param queryTypeFunction the function to use. - * @param

the payload type. - * @return the spec - */ - public

R2dbcMessageHandlerSpec queryTypeFunction( - Function, R2dbcMessageHandler.Type> queryTypeFunction) { - - return queryTypeExpression(new FunctionExpression<>(queryTypeFunction)); - } - - /** - * Set a SpEL expression to evaluate a {@link R2dbcMessageHandler.Type} for query to execute. - * @param queryTypeExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec queryTypeExpression(String queryTypeExpression) { - return queryTypeExpression(PARSER.parseExpression(queryTypeExpression)); - } - - /** - * Set a SpEL expression to evaluate a {@link R2dbcMessageHandler.Type} for query to execute. - * @param queryTypeExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec queryTypeExpression(Expression queryTypeExpression) { - this.reactiveMessageHandler.setQueryTypeExpression(queryTypeExpression); - return this; - } - - /** - * Specify a table in the target database to execute the query. - * @param tableName the name of the table to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec tableName(String tableName) { - this.reactiveMessageHandler.setTableName(tableName); - return this; - } - - /** - * Set a {@link Function} to evaluate a table name at runtime against request message. - * @param tableNameFunction the function to use. - * @param

the payload type. - * @return the spec - */ - public

R2dbcMessageHandlerSpec tableNameFunction(Function, String> tableNameFunction) { - return tableNameExpression(new FunctionExpression<>(tableNameFunction)); - } - - /** - * Set a SpEL expression to evaluate a table name at runtime against request message. - * @param tableNameExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec tableNameExpression(String tableNameExpression) { - return tableNameExpression(PARSER.parseExpression(tableNameExpression)); - } - - /** - * Set a SpEL expression to evaluate a table name at runtime against request message. - * @param tableNameExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec tableNameExpression(Expression tableNameExpression) { - this.reactiveMessageHandler.setTableNameExpression(tableNameExpression); - return this; - } - - /** - * Set a {@link Function} to evaluate a {@link Map} for name-value pairs to bind as parameters - * into a query. - * @param valuesFunction the function to use. - * @param

the payload type. - * @return the spec - */ - public

R2dbcMessageHandlerSpec values(Function, Map> valuesFunction) { - return values(new FunctionExpression<>(valuesFunction)); - } - - /** - * Set a SpEL expression to evaluate a {@link Map} for name-value pairs to bind as parameters - * into a query. - * @param valuesExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec values(String valuesExpression) { - return values(PARSER.parseExpression(valuesExpression)); - } - - /** - * Set a SpEL expression to evaluate a {@link Map} for name-value pairs to bind as parameters - * into a query. - * @param valuesExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec values(Expression valuesExpression) { - this.reactiveMessageHandler.setValuesExpression(valuesExpression); - return this; - } - - /** - * Set a {@link Function} to evaluate a {@link Criteria} for query to execute. - * @param criteriaFunction the function to use. - * @param

the payload type. - * @return the spec - */ - public

R2dbcMessageHandlerSpec criteria(Function, Criteria> criteriaFunction) { - return criteria(new FunctionExpression<>(criteriaFunction)); - } - - /** - * Set a SpEL expression to evaluate a {@link Criteria} for query to execute. - * @param criteriaExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec criteria(String criteriaExpression) { - return criteria(PARSER.parseExpression(criteriaExpression)); - } - - /** - * Set a SpEL expression to evaluate a {@link Criteria} for query to execute. - * @param criteriaExpression the expression to use. - * @return the spec - */ - public R2dbcMessageHandlerSpec criteria(Expression criteriaExpression) { - this.reactiveMessageHandler.setCriteriaExpression(criteriaExpression); - return this; - } - -} diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbcMessageSourceSpec.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbcMessageSourceSpec.java deleted file mode 100644 index 8669106f598..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/R2dbcMessageSourceSpec.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.dsl; - -import java.util.function.BiFunction; - -import org.springframework.data.r2dbc.core.R2dbcEntityOperations; -import org.springframework.expression.Expression; -import org.springframework.integration.dsl.MessageSourceSpec; -import org.springframework.integration.r2dbc.inbound.R2dbcMessageSource; -import org.springframework.r2dbc.core.DatabaseClient; - -/** - * The {@link MessageSourceSpec} for the {@link R2dbcMessageSource}. - * - * @author Artem Bilan - * - * @since 5.4 - */ -public class R2dbcMessageSourceSpec extends MessageSourceSpec { - - protected R2dbcMessageSourceSpec(R2dbcEntityOperations r2dbcEntityOperations, String query) { - this.target = new R2dbcMessageSource(r2dbcEntityOperations, query); - } - - protected R2dbcMessageSourceSpec(R2dbcEntityOperations r2dbcEntityOperations, Expression queryExpression) { - this.target = new R2dbcMessageSource(r2dbcEntityOperations, queryExpression); - } - - /** - * Set the expected payload type. - * @param payloadType the class to use. - * @return the spec - */ - public R2dbcMessageSourceSpec payloadType(Class payloadType) { - this.target.setPayloadType(payloadType); - return this; - } - - /** - * Configure an update query. - * @param updateSql the update query string. - * @return the spec - */ - public R2dbcMessageSourceSpec updateSql(String updateSql) { - this.target.setUpdateSql(updateSql); - return this; - } - - /** - * Set a {@link BiFunction} which is used to bind parameters into the update query. - * @param bindFunction the {@link BiFunction} to use. - * @return the spec - */ - public R2dbcMessageSourceSpec bindFunction( - BiFunction bindFunction) { - - this.target.setBindFunction(bindFunction); - return this; - } - - /** - * The flag to manage which find* method to invoke on {@link R2dbcEntityOperations}. - * @param expectSingleResult true if a single result is expected. - * @return the spec - */ - public R2dbcMessageSourceSpec expectSingleResult(boolean expectSingleResult) { - this.target.setExpectSingleResult(expectSingleResult); - return this; - } - -} diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/package-info.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/package-info.java deleted file mode 100644 index 54a3ccc06df..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for supporting Java DSL for R2DBC components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.r2dbc.dsl; diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/inbound/R2dbcMessageSource.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/inbound/R2dbcMessageSource.java deleted file mode 100644 index 5e05456e94b..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/inbound/R2dbcMessageSource.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.inbound; - -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -import io.r2dbc.spi.Row; -import io.r2dbc.spi.RowMetadata; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.data.r2dbc.convert.EntityRowMapper; -import org.springframework.data.r2dbc.core.R2dbcEntityOperations; -import org.springframework.data.r2dbc.core.StatementMapper; -import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.endpoint.AbstractMessageSource; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.r2dbc.core.ColumnMapRowMapper; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.r2dbc.core.RowsFetchSpec; -import org.springframework.util.Assert; - -/** - * An instance of {@link org.springframework.integration.core.MessageSource} which returns - * a {@link org.springframework.messaging.Message} with a payload which is the result of - * execution of query. When {@code expectSingleResult} is false (default), the R2DBC - * query is executed returning a {@link reactor.core.publisher.Flux}. - * The returned {@link reactor.core.publisher.Flux} will be used as the payload of the - * {@link org.springframework.messaging.Message} returned by the {@link #receive()} - * method. - *

- * When {@code expectSingleResult} is true, the query is executed returning a {@link reactor.core.publisher.Mono} - * for the single object returned from the query. - * - * @author Rohan Mukesh - * @author Artem Bilan - * - * @since 5.4 - */ -public class R2dbcMessageSource extends AbstractMessageSource> { - - private final R2dbcEntityOperations r2dbcEntityOperations; - - private final DatabaseClient databaseClient; - - private final StatementMapper statementMapper; - - private final SelectCreator selectCreator = new SelectCreator(); - - private final Expression queryExpression; - - private Class payloadType = Map.class; - - private BiFunction rowMapper = ColumnMapRowMapper.INSTANCE; - - private boolean expectSingleResult = false; - - private StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - - @Nullable - private String updateSql; - - @Nullable - private BiFunction bindFunction; - - private volatile boolean initialized = false; - - /** - * Create an instance with the provided {@link R2dbcEntityOperations} and SpEL expression - * which should resolve to a Relational 'query' string. - * It assumes that the {@link R2dbcEntityOperations} is fully initialized and ready to be used. - * The 'query' will be evaluated on every call to the {@link #receive()} method. - * @param r2dbcEntityOperations The reactive database client for performing database calls. - * @param query The query String. - */ - public R2dbcMessageSource(R2dbcEntityOperations r2dbcEntityOperations, String query) { - this(r2dbcEntityOperations, new LiteralExpression(query)); - } - - /** - * Create an instance with the provided {@link R2dbcEntityOperations} and SpEL expression - * which should resolve to a query string or {@link StatementMapper.SelectSpec} instance. - * It assumes that the {@link R2dbcEntityOperations} is fully initialized and ready to be used. - * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * @param r2dbcEntityOperations The reactive for performing database calls. - * @param queryExpression The query expression. The root object for evaluation context is a {@link SelectCreator} - * for delegation int the {@link StatementMapper#createSelect} fluent API. - */ - @SuppressWarnings("deprecation") - public R2dbcMessageSource(R2dbcEntityOperations r2dbcEntityOperations, Expression queryExpression) { - Assert.notNull(r2dbcEntityOperations, "'r2dbcEntityOperations' must not be null"); - Assert.notNull(queryExpression, "'queryExpression' must not be null"); - this.r2dbcEntityOperations = r2dbcEntityOperations; - this.databaseClient = r2dbcEntityOperations.getDatabaseClient(); - this.statementMapper = r2dbcEntityOperations.getDataAccessStrategy().getStatementMapper(); - this.queryExpression = queryExpression; - } - - /** - * Set the type of the entityClass which is used for the {@link EntityRowMapper}. - * @param payloadType The class to use. - */ - public void setPayloadType(Class payloadType) { - Assert.notNull(payloadType, "'payloadType' must not be null"); - this.payloadType = payloadType; - } - - /** - * Set an update query that will be passed to the {@link DatabaseClient#sql(String)} method. - * @param updateSql the update query string. - */ - public void setUpdateSql(String updateSql) { - this.updateSql = updateSql; - } - - /** - * Set a {@link BiFunction} which is used to bind parameters into the update query. - * @param bindFunction the {@link BiFunction} to use. - */ - @SuppressWarnings("unchecked") - public void setBindFunction( - BiFunction bindFunction) { - - this.bindFunction = - (BiFunction) bindFunction; - } - - /** - * The flag to manage which find* method to invoke on {@link R2dbcEntityOperations}. - * Default is 'false', which means the {@link #receive()} method will use - * the {@link DatabaseClient#sql(String)} method and will fetch all. If set - * to 'true'{@link #receive()} will use {@link DatabaseClient#sql(String)} - * and will fetch one and the payload of the returned {@link org.springframework.messaging.Message} - * will be the returned target Object of type - * identified by {@link #payloadType} instead of a List. - * @param expectSingleResult true if a single result is expected. - */ - public void setExpectSingleResult(boolean expectSingleResult) { - this.expectSingleResult = expectSingleResult; - } - - @Override - public String getComponentType() { - return "r2dbc:inbound-channel-adapter"; - } - - @Override - protected void onInit() { - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - if (!Map.class.isAssignableFrom(this.payloadType)) { - this.rowMapper = new EntityRowMapper<>(this.payloadType, this.r2dbcEntityOperations.getConverter()); - } - this.initialized = true; - } - - /** - * Execute a query returning its results as the Message payload. - * The payload can be either {@link reactor.core.publisher.Flux} or - * {@link reactor.core.publisher.Mono} of objects of type identified by {@link #payloadType}, - * or a single element of type identified by {@link #payloadType} - * based on the value of {@link #expectSingleResult} attribute which defaults to 'false' resulting - * {@link org.springframework.messaging.Message} with payload of type - * {@link reactor.core.publisher.Flux}. - */ - @Override - protected Object doReceive() { - Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method"); - Mono> queryMono = - Mono.fromSupplier(() -> - this.queryExpression.getValue(this.evaluationContext, this.selectCreator)) - .map(this::prepareFetch); - if (this.expectSingleResult) { - return queryMono.flatMap(RowsFetchSpec::one) - .flatMap(this::executeUpdate); - } - - return queryMono.flatMapMany(RowsFetchSpec::all) - .flatMap(this::executeUpdate); - } - - private Mono executeUpdate(Object result) { - if (this.updateSql != null) { - DatabaseClient.GenericExecuteSpec genericExecuteSpec = this.databaseClient.sql(this.updateSql); - if (this.bindFunction != null) { - genericExecuteSpec = this.bindFunction.apply(genericExecuteSpec, result); - } - return genericExecuteSpec.then() - .thenReturn(result); - } - return Mono.just(result); - } - - private RowsFetchSpec prepareFetch(Object queryObject) { - Supplier query = evaluateQueryObject(queryObject); - return this.databaseClient.sql(query) - .map(this.rowMapper); - } - - private Supplier evaluateQueryObject(Object queryObject) { - if (queryObject instanceof String) { - return () -> (String) queryObject; - } - else if (queryObject instanceof StatementMapper.SelectSpec) { - return this.statementMapper.getMappedObject((StatementMapper.SelectSpec) queryObject); - } - throw new IllegalStateException("'queryExpression' must evaluate to String " + - "or org.springframework.data.r2dbc.core.StatementMapper.SelectSpec, but not: " + queryObject); - } - - /** - * An instance of this class is used as a root object for query expression - * to give a limited access only to the {@link StatementMapper#createSelect} fluent API. - */ - public class SelectCreator { - - SelectCreator() { - } - - public StatementMapper.SelectSpec createSelect(String table) { - return R2dbcMessageSource.this.statementMapper.createSelect(table); - } - - public StatementMapper.SelectSpec createSelect(SqlIdentifier table) { - return R2dbcMessageSource.this.statementMapper.createSelect(table); - } - - } - -} diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/inbound/package-info.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/inbound/package-info.java deleted file mode 100644 index 70df90413f6..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for supporting R2DBC inbound components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.r2dbc.inbound; diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/outbound/R2dbcMessageHandler.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/outbound/R2dbcMessageHandler.java deleted file mode 100644 index 23e13061539..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/outbound/R2dbcMessageHandler.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.outbound; - -import java.util.HashMap; -import java.util.Map; - -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; - -import org.springframework.data.r2dbc.core.R2dbcEntityOperations; -import org.springframework.data.r2dbc.core.StatementMapper; -import org.springframework.data.relational.core.query.Criteria; -import org.springframework.data.relational.core.query.Update; -import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.expression.Expression; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.expression.spel.support.StandardTypeLocator; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.handler.AbstractReactiveMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.r2dbc.core.Parameter; -import org.springframework.r2dbc.core.PreparedOperation; -import org.springframework.util.Assert; - -/** - * Implementation of {@link org.springframework.messaging.ReactiveMessageHandler} which writes - * Message payload into a Relational Database, using reactive r2dbc support. - * - * @author Rohan Mukesh - * @author Artem Bilan - * - * @since 5.4 - */ -public class R2dbcMessageHandler extends AbstractReactiveMessageHandler { - - private final R2dbcEntityOperations r2dbcEntityOperations; - - private final StatementMapper statementMapper; - - private StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - - private Expression queryTypeExpression = new ValueExpression<>(Type.INSERT); - - @Nullable - private Expression tableNameExpression; - - @Nullable - private Expression valuesExpression; - - @Nullable - private Expression criteriaExpression; - - private volatile boolean initialized = false; - - /** - * Construct this instance using a fully created and initialized instance of provided - * {@link R2dbcEntityOperations}. - * @param r2dbcEntityOperations The R2dbcEntityOperations implementation. - */ - @SuppressWarnings("deprecation") - public R2dbcMessageHandler(R2dbcEntityOperations r2dbcEntityOperations) { - Assert.notNull(r2dbcEntityOperations, "'r2dbcEntityOperations' must not be null"); - this.r2dbcEntityOperations = r2dbcEntityOperations; - this.statementMapper = this.r2dbcEntityOperations.getDataAccessStrategy().getStatementMapper(); - } - - /** - * Set a {@link R2dbcMessageHandler.Type} for query to execute. - * @param type the {@link R2dbcMessageHandler.Type} to use. - */ - public void setQueryType(R2dbcMessageHandler.Type type) { - setQueryTypeExpression(new ValueExpression<>(type)); - } - - /** - * Set a SpEL expression to evaluate a {@link R2dbcMessageHandler.Type} for query to execute. - * @param queryTypeExpression the expression to use. - */ - public void setQueryTypeExpression(Expression queryTypeExpression) { - Assert.notNull(queryTypeExpression, "'queryTypeExpression' must not be null"); - this.queryTypeExpression = queryTypeExpression; - } - - /** - * Specify a table in the target database to execute the query. - * @param tableName the name of the table to use. - */ - public void setTableName(String tableName) { - setTableNameExpression(new LiteralExpression(tableName)); - } - - /** - * Set a SpEL expression to evaluate a table name at runtime against request message. - * @param tableNameExpression the expression to use. - */ - public void setTableNameExpression(Expression tableNameExpression) { - this.tableNameExpression = tableNameExpression; - } - - /** - * Set a SpEL expression to evaluate a {@link Map} for name-value pairs to bind as parameters - * into a query. - * @param valuesExpression the expression to use. - */ - public void setValuesExpression(Expression valuesExpression) { - this.valuesExpression = valuesExpression; - } - - /** - * Set a SpEL expression to evaluate a {@link Criteria} for query to execute. - * @param criteriaExpression the expression to use. - */ - public void setCriteriaExpression(Expression criteriaExpression) { - this.criteriaExpression = criteriaExpression; - } - - @Override - public String getComponentType() { - return "r2dbc:outbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - TypeLocator typeLocator = this.evaluationContext.getTypeLocator(); - if (typeLocator instanceof StandardTypeLocator) { - //Register R2dbc criteria API package so FQCN can be avoided in query-expression. - ((StandardTypeLocator) typeLocator).registerImport("org.springframework.data.relational.core.query"); - } - this.initialized = true; - - } - - @Override - protected Mono handleMessageInternal(Message message) { - Assert.isTrue(this.initialized, "The instance is not yet initialized. Invoke its afterPropertiesSet() method"); - return Mono.fromSupplier(() -> this.queryTypeExpression.getValue(this.evaluationContext, message, Type.class)) - .flatMap(mode -> - switch (mode) { - case INSERT -> handleInsert(message); - case UPDATE -> handleUpdate(message); - case DELETE -> handleDelete(message); - }) - .then(); - } - - private Mono handleDelete(Message message) { - if (this.tableNameExpression != null) { - String tableName = this.tableNameExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(tableName, "'tableNameExpression' must not evaluate to null"); - Criteria criteria = evaluateCriteriaExpression(message); - StatementMapper.DeleteSpec deleteSpec = - this.statementMapper.createDelete(tableName) - .withCriteria(criteria); - PreparedOperation operation = this.statementMapper.getMappedObject(deleteSpec); - return this.r2dbcEntityOperations.getDatabaseClient() - .sql(operation) - .then(); - } - else { - return this.r2dbcEntityOperations.delete(message.getPayload()) - .then(); - } - } - - private Mono handleUpdate(Message message) { - if (this.tableNameExpression != null) { - String tableName = this.tableNameExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(tableName, "'tableNameExpression' must not evaluate to null"); - Map values = evaluateValuesExpression(message); - Map updateMap = transformIntoSqlIdentifierMap(values); - Criteria criteria = evaluateCriteriaExpression(message); - - StatementMapper.UpdateSpec updateSpec = - this.statementMapper.createUpdate(tableName, Update.from(updateMap)) - .withCriteria(criteria); - PreparedOperation operation = this.statementMapper.getMappedObject(updateSpec); - return this.r2dbcEntityOperations.getDatabaseClient() - .sql(operation) - .then(); - } - else { - return this.r2dbcEntityOperations.update(message.getPayload()) - .then(); - } - } - - private Map transformIntoSqlIdentifierMap(Map values) { - Map sqlIdentifierObjectMap = new HashMap<>(); - values.forEach((k, v) -> sqlIdentifierObjectMap.put(SqlIdentifier.unquoted(k), v)); - return sqlIdentifierObjectMap; - } - - @SuppressWarnings("deprecation") - private Mono handleInsert(Message message) { - if (this.tableNameExpression != null) { - String tableName = this.tableNameExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(tableName, "'tableNameExpression' must not evaluate to null"); - Map values = evaluateValuesExpression(message); - - StatementMapper.InsertSpec insertSpec = this.statementMapper.createInsert(tableName); - - for (Map.Entry entry : values.entrySet()) { - insertSpec = insertSpec.withColumn(entry.getKey(), - Parameter.fromOrEmpty(entry.getValue(), Object.class)); - } - - PreparedOperation operation = this.statementMapper.getMappedObject(insertSpec); - return this.r2dbcEntityOperations.getDatabaseClient() - .sql(operation) - .then(); - } - else { - return this.r2dbcEntityOperations.insert(message.getPayload()) - .then(); - } - } - - @SuppressWarnings("unchecked") - private Map evaluateValuesExpression(Message message) { - Assert.notNull(this.valuesExpression, - "'this.valuesExpression' must not be null when 'tableNameExpression' mode is used"); - Map fieldValues = - (Map) this.valuesExpression.getValue(this.evaluationContext, message, Map.class); - Assert.notNull(fieldValues, "'valuesExpression' must not evaluate to null"); - return fieldValues; - } - - private Criteria evaluateCriteriaExpression(Message message) { - Assert.notNull(this.criteriaExpression, - "'this.criteriaExpression' must not be null when 'tableNameExpression' mode is used"); - Criteria criteria = this.criteriaExpression.getValue(this.evaluationContext, message, Criteria.class); - Assert.notNull(criteria, "'criteriaExpression' must not evaluate to null"); - return criteria; - } - - /** - * /** - * The mode for the {@link R2dbcMessageHandler}. - */ - public enum Type { - - /** - * Set a {@link R2dbcMessageHandler} into an {@code insert} mode. - */ - INSERT, - - /** - * Set a {@link R2dbcMessageHandler} into an {@code update} mode. - */ - UPDATE, - - /** - * Set a {@link R2dbcMessageHandler} into a {@code delete} mode. - */ - DELETE, - - } - -} diff --git a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/outbound/package-info.java b/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/outbound/package-info.java deleted file mode 100644 index 17c581a5654..00000000000 --- a/spring-integration-r2dbc/src/main/java/org/springframework/integration/r2dbc/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for supporting R2DBC outbound components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.r2dbc.outbound; diff --git a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/config/R2dbcDatabaseConfiguration.java b/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/config/R2dbcDatabaseConfiguration.java deleted file mode 100644 index 86f57b0aba2..00000000000 --- a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/config/R2dbcDatabaseConfiguration.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.config; - -import io.r2dbc.h2.H2ConnectionConfiguration; -import io.r2dbc.h2.H2ConnectionFactory; -import io.r2dbc.spi.ConnectionFactory; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; -import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; -import org.springframework.data.r2dbc.dialect.H2Dialect; -import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; -import org.springframework.r2dbc.core.DatabaseClient; - -/** - * @author Rohan Mukesh - * @author Artem Bilan - * - * @since 5.4 - */ -@Configuration -@EnableR2dbcRepositories(basePackages = "org.springframework.integration.r2dbc.repository") -public class R2dbcDatabaseConfiguration extends AbstractR2dbcConfiguration { - - @Bean - @Override - public ConnectionFactory connectionFactory() { - return createConnectionFactory(); - } - - public static ConnectionFactory createConnectionFactory() { - - return new H2ConnectionFactory(H2ConnectionConfiguration.builder() - .inMemory("r2dbc") - .username("sa") - .password("") - .option("DB_CLOSE_DELAY=-1").build()); - } - - @Bean - public DatabaseClient databaseClient(ConnectionFactory connectionFactory) { - return DatabaseClient.create(connectionFactory); - } - - @Bean - public R2dbcEntityTemplate r2dbcEntityTemplate(DatabaseClient databaseClient) { - return new R2dbcEntityTemplate(databaseClient, H2Dialect.INSTANCE); - } - -} diff --git a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/dsl/R2dbcDslTests.java b/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/dsl/R2dbcDslTests.java deleted file mode 100644 index d49209d070e..00000000000 --- a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/dsl/R2dbcDslTests.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.dsl; - -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.Lifecycle; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; -import org.springframework.data.relational.core.query.Criteria; -import org.springframework.data.relational.core.query.Query; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.MessageChannels; -import org.springframework.integration.r2dbc.config.R2dbcDatabaseConfiguration; -import org.springframework.integration.r2dbc.entity.Person; -import org.springframework.integration.r2dbc.outbound.R2dbcMessageHandler; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.awaitility.Awaitility.await; - -/** - * @author Artem Bilan - * - * @since 5.4 - */ -@SpringJUnitConfig -@DirtiesContext -public class R2dbcDslTests { - - @Autowired - R2dbcEntityTemplate r2dbcEntityTemplate; - - @Autowired - Lifecycle r2dbcInboundChannelAdapter; - - @BeforeEach - public void setup() { - List statements = - Arrays.asList( - "DROP TABLE IF EXISTS person;", - "CREATE table person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR2, age INT NOT NULL);"); - - DatabaseClient databaseClient = this.r2dbcEntityTemplate.getDatabaseClient(); - statements.forEach(it -> - databaseClient.sql(it) - .fetch() - .rowsUpdated() - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete()); - } - - @Test - void testR2DbcDsl() { - this.r2dbcInboundChannelAdapter.start(); - - this.r2dbcEntityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - await().until(() -> - this.r2dbcEntityTemplate.select(Person.class) - .matching(Query.query(Criteria.where("age").is(36))) - .one() - .block(Duration.ofMillis(100)) != null); - } - - @Configuration - @EnableIntegration - @Import(R2dbcDatabaseConfiguration.class) - static class R2dbcMessageSourceConfiguration { - - @Bean - IntegrationFlow r2dbcDslFlow(R2dbcEntityTemplate r2dbcEntityTemplate) { - return IntegrationFlow - .from(R2dbc.inboundChannelAdapter(r2dbcEntityTemplate, - (selectCreator) -> - selectCreator.createSelect("person") - .withProjection("*") - .withCriteria(Criteria.where("id").is(1))) - .expectSingleResult(true) - .payloadType(Person.class) - .updateSql("UPDATE Person SET id='2' where id = :id") - .bindFunction((DatabaseClient.GenericExecuteSpec bindSpec, Person o) -> - bindSpec.bind("id", o.getId())), - e -> e.poller(p -> p.fixedDelay(100)).autoStartup(false).id("r2dbcInboundChannelAdapter")) - .handle((p, h) -> p) - .channel(MessageChannels.flux()) - .handleReactive( - R2dbc.outboundChannelAdapter(r2dbcEntityTemplate) - .queryType(R2dbcMessageHandler.Type.UPDATE) - .tableNameExpression("payload.class.simpleName") - .criteria((message) -> Criteria.where("id").is(2)) - .values("{age:36}")); - } - - } - -} diff --git a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/entity/Person.java b/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/entity/Person.java deleted file mode 100644 index 6fd7216de7e..00000000000 --- a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/entity/Person.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.entity; - -import java.util.Objects; - -import org.springframework.data.annotation.Id; -import org.springframework.data.relational.core.mapping.Table; - -/** - * @author Rohan Mukesh - * @author Artem Bilan - * - * @since 5.4 - */ -@Table -public class Person { - - @Id - private Integer id; - - private String name; - - private Integer age; - - public void setId(Integer id) { - this.id = id; - } - - public void setName(String name) { - this.name = name; - } - - public void setAge(Integer age) { - this.age = age; - } - - public Person(String name, Integer age) { - this.name = name; - this.age = age; - } - - public Integer getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public Integer getAge() { - return this.age; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Person person = (Person) o; - return Objects.equals(this.id, person.id) && - Objects.equals(this.name, person.name) && - Objects.equals(this.age, person.age); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.age); - } - - @Override - public String toString() { - return "Person{" + - "id=" + this.id + - ", name='" + this.name + '\'' + - ", age=" + this.age + - '}'; - } - -} diff --git a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/inbound/R2dbcMessageSourceTests.java b/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/inbound/R2dbcMessageSourceTests.java deleted file mode 100644 index 46dc12bd1d2..00000000000 --- a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/inbound/R2dbcMessageSourceTests.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.inbound; - -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.context.expression.MapAccessor; -import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; -import org.springframework.data.r2dbc.dialect.H2Dialect; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.r2dbc.config.R2dbcDatabaseConfiguration; -import org.springframework.integration.r2dbc.entity.Person; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rohan Mukesh - * @author Artem Bilan - * - * @since 5.4 - */ -@SpringJUnitConfig -@DirtiesContext -public class R2dbcMessageSourceTests { - - @Autowired - DatabaseClient client; - - R2dbcEntityTemplate entityTemplate; - - @Autowired - R2dbcMessageSource defaultR2dbcMessageSource; - - @Autowired - R2dbcMessageSource r2dbcMessageSourceSelectOne; - - @Autowired - R2dbcMessageSource r2dbcMessageSourceSelectMany; - - @Autowired - R2dbcMessageSource r2dbcMessageSourceError; - - @BeforeEach - public void setup() { - this.entityTemplate = new R2dbcEntityTemplate(this.client, H2Dialect.INSTANCE); - r2dbcMessageSourceSelectMany.setExpectSingleResult(false); - defaultR2dbcMessageSource.setBindFunction(null); - - List statements = - Arrays.asList( - "DROP TABLE IF EXISTS person;", - "CREATE table person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR2, age INT NOT NULL);"); - - statements.forEach(it -> this.client.sql(it) - .fetch() - .rowsUpdated() - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete()); - } - - @Test - public void validateComponentType() { - assertThat(this.defaultR2dbcMessageSource.getComponentType()) - .isEqualTo("r2dbc:inbound-channel-adapter"); - } - - @Test - public void validateSuccessfulQueryWithoutSettingExpectedElement() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - StepVerifier.create((Flux) r2dbcMessageSourceSelectMany.receive().getPayload()) - .assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob")) - .verifyComplete(); - } - - @Test - public void validateSuccessfulQueryWithSingleElementOfMonoDBObject() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - r2dbcMessageSourceSelectOne.setExpectSingleResult(true); - - StepVerifier.create((Mono) r2dbcMessageSourceSelectOne.receive().getPayload()) - .assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob")) - .verifyComplete(); - - } - - @Test - public void validateSuccessfulQueryWithMultipleElementOfFluxDBObject() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - this.entityTemplate.insert(new Person("Tom", 40)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - StepVerifier.create((Flux) r2dbcMessageSourceSelectMany.receive().getPayload()) - .assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob")) - .assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Tom")) - .verifyComplete(); - - } - - @Test - public void validateSuccessfulUpdateWithSingleElementOfMonoDBObject() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - r2dbcMessageSourceSelectMany.setUpdateSql("UPDATE Person SET name='Foo' where age = :age"); - r2dbcMessageSourceSelectMany.setBindFunction( - (DatabaseClient.GenericExecuteSpec bindSpec, Person o) -> bindSpec.bind("age", o.getAge())); - r2dbcMessageSourceSelectMany.setExpectSingleResult(true); - - StepVerifier.create(r2dbcMessageSourceSelectMany.receive().getPayload()) - .assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob")) - .verifyComplete(); - - this.entityTemplate.select(Person.class) - .all() - .as(StepVerifier::create) - .assertNext(person -> assertThat(person.getName()).isEqualTo("Foo")) - .verifyComplete(); - - } - - @Test - public void validateSuccessfulUpdateWithMultiplesElementsOfFluxDBObject() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - this.entityTemplate.insert(new Person("Tom", 40)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - r2dbcMessageSourceSelectMany.setUpdateSql("UPDATE person SET name='Foo' where id = :id"); - r2dbcMessageSourceSelectMany.setBindFunction( - (DatabaseClient.GenericExecuteSpec bindSpec, Person o) -> bindSpec.bind("id", o.getId())); - StepVerifier.create(r2dbcMessageSourceSelectMany.receive().getPayload()) - .expectNextCount(2) - .verifyComplete(); - - this.entityTemplate.select(Person.class) - .all() - .as(StepVerifier::create) - .assertNext(person -> assertThat(person.getName()).isEqualTo("Foo")) - .assertNext(person -> assertThat(person.getName()).isEqualTo("Foo")) - .verifyComplete(); - - } - - @Test - public void validateSuccessfulUpdateWithoutBindFunction() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - this.entityTemplate.insert(new Person("Tom", 40)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - r2dbcMessageSourceSelectMany.setUpdateSql("UPDATE person SET name='Foo' where id = 1"); - - StepVerifier.create(r2dbcMessageSourceSelectMany.receive().getPayload()) - .expectNextCount(2) - .verifyComplete(); - - this.entityTemplate.select(Person.class) - .all() - .as(StepVerifier::create) - .assertNext(person -> assertThat(person.getName()).isEqualTo("Foo")) - .assertNext(person -> assertThat(person.getName()).isEqualTo("Tom")) - .verifyComplete(); - - } - - @Test - public void validateSuccessfulUpdateWithoutPayloadType() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - this.entityTemplate.insert(new Person("Tom", 40)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - defaultR2dbcMessageSource.setUpdateSql("UPDATE person SET name='Foo' where id = 1"); - - StepVerifier.create(defaultR2dbcMessageSource.receive().getPayload()) - .expectNextCount(2) - .verifyComplete(); - - this.client.sql("select * from person") - .fetch() - .all() - .as(StepVerifier::create) - .assertNext(person -> assertThat(person.get("name")).isEqualTo("Foo")) - .assertNext(person -> assertThat(person.get("name")).isEqualTo("Tom")) - .verifyComplete(); - - } - - @Test - public void testWrongPayloadTypeInBindFunction() { - this.entityTemplate.insert(new Person("Bob", 35)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - this.entityTemplate.insert(new Person("Tom", 40)) - .then() - .as(StepVerifier::create) - .verifyComplete(); - - defaultR2dbcMessageSource.setUpdateSql("UPDATE person SET name='Foo' where id = 1"); - defaultR2dbcMessageSource.setBindFunction( - (DatabaseClient.GenericExecuteSpec bindSpec, Person o) -> bindSpec.bind("id", o.getId())); - - StepVerifier.create(defaultR2dbcMessageSource.receive().getPayload()) - .expectErrorMatches(throwable -> throwable instanceof ClassCastException) - .verify(); - - } - - @Test - public void testAnyOtherObjectQueryExpression() { - - StepVerifier.create((Flux) r2dbcMessageSourceError.receive().getPayload()) - .expectErrorMatches(throwable -> throwable instanceof IllegalStateException - && throwable.getMessage().contains("'queryExpression' must evaluate to String or")) - .verify(Duration.ofSeconds(10)); - } - - @Configuration - @Import(R2dbcDatabaseConfiguration.class) - static class R2dbcMessageSourceConfiguration { - - @Autowired - R2dbcEntityTemplate r2dbcEntityTemplate; - - @Bean - public R2dbcMessageSource defaultR2dbcMessageSource() { - return new R2dbcMessageSource(r2dbcEntityTemplate, "select * from " + - "person"); - } - - @Bean - public R2dbcMessageSource r2dbcMessageSourceSelectOne() { - R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(this.r2dbcEntityTemplate, - "select * from person Where id = 1"); - r2dbcMessageSource.setPayloadType(Person.class); - return r2dbcMessageSource; - } - - @Bean - public R2dbcMessageSource r2dbcMessageSourceSelectMany() { - R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(this.r2dbcEntityTemplate, - "select * from person"); - r2dbcMessageSource.setPayloadType(Person.class); - return r2dbcMessageSource; - } - - @Bean - public R2dbcMessageSource r2dbcMessageSourceError() { - R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(this.r2dbcEntityTemplate, - new ValueExpression<>(new Object())); - r2dbcMessageSource.setPayloadType(Person.class); - return r2dbcMessageSource; - } - - @Bean - public StandardEvaluationContext integrationEvaluationContext(ApplicationContext applicationContext) { - StandardEvaluationContext integrationEvaluationContext = new StandardEvaluationContext(); - integrationEvaluationContext.addPropertyAccessor(new MapAccessor()); - integrationEvaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext)); - return integrationEvaluationContext; - } - - } - -} diff --git a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/outbound/R2dbcMessageHandlerTests.java b/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/outbound/R2dbcMessageHandlerTests.java deleted file mode 100644 index 463bafd682f..00000000000 --- a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/outbound/R2dbcMessageHandlerTests.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.outbound; - -import java.time.Duration; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.junit.Assert; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.context.expression.MapAccessor; -import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; -import org.springframework.data.relational.core.query.Criteria; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.r2dbc.config.R2dbcDatabaseConfiguration; -import org.springframework.integration.r2dbc.entity.Person; -import org.springframework.integration.r2dbc.repository.PersonRepository; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rohan Mukesh - * @author Artem Bilan - * - * @since 5.4 - */ -@SpringJUnitConfig -@DirtiesContext -public class R2dbcMessageHandlerTests { - - @Autowired - DatabaseClient client; - - @Autowired - R2dbcEntityTemplate r2dbcEntityTemplate; - - @Autowired - PersonRepository personRepository; - - @Autowired - R2dbcMessageHandler r2dbcMessageHandler; - - @BeforeEach - public void setup() { - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT); - this.r2dbcMessageHandler.setTableNameExpression(null); - List statements = Arrays.asList( - "DROP TABLE IF EXISTS person;", - "CREATE table person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR2, age INT NOT NULL);"); - - statements.forEach(it -> client.sql(it) - .fetch() - .rowsUpdated() - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete()); - } - - @Test - public void validateMessageHandlingWithDefaultInsertCollection() { - Message message = MessageBuilder.withPayload(createPerson("Bob", 35)).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - this.personRepository.findAll() - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); - } - - @Test - public void validateMessageHandlingWithInsertQueryCollection() { - this.r2dbcMessageHandler.setValuesExpression(new FunctionExpression>(Message::getPayload)); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT); - this.r2dbcMessageHandler.setTableName("person"); - Map payload = new HashMap<>(); - payload.put("name", "rohan"); - payload.put("age", 35); - Message message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - this.client.sql("SELECT name, age FROM person") - .fetch().all() - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); - - } - - @Test - public void validateComponentType() { - assertThat(this.r2dbcMessageHandler.getComponentType()).isEqualTo("r2dbc:outbound-channel-adapter"); - } - - @Test - public void validateMessageHandlingWithDefaultUpdateCollection() { - Message message = MessageBuilder.withPayload(createPerson("Bob", 35)).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.UPDATE); - - Person person = - this.r2dbcEntityTemplate.select(Person.class) - .first() - .block(); - - person.setAge(40); - - message = MessageBuilder.withPayload(person) - .build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - this.personRepository.findAll() - .as(StepVerifier::create) - .consumeNextWith(p -> Assert.assertEquals(Optional.of(40), Optional.ofNullable(p.getAge()))) - .verifyComplete(); - } - - @Test - public void validateMessageHandlingWithUpdateQueryCollection() { - this.r2dbcMessageHandler.setValuesExpression(new FunctionExpression>(Message::getPayload)); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT); - this.r2dbcMessageHandler.setTableName("person"); - Map payload = new HashMap<>(); - payload.put("name", "Bob"); - payload.put("age", 35); - Message message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - payload = new HashMap<>(); - payload.put("name", "Rob"); - payload.put("age", 43); - message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - payload = new HashMap<>(); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.UPDATE); - - Object insertedId = client.sql("SELECT id FROM person") - .fetch() - .first() - .block() - .get("id"); - - this.r2dbcMessageHandler.setCriteriaExpression( - new FunctionExpression>((m) -> Criteria.where("id").is(insertedId))); - payload.put("age", 40); - - message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - this.client.sql("SELECT age,name FROM person where age=40") - .fetch().all() - .as(StepVerifier::create) - .consumeNextWith(response -> Assert.assertEquals("{AGE=40, NAME=Bob}", response.toString())) - .verifyComplete(); - - } - - @Test - public void validateMessageHandlingWithDefaultDeleteCollection() { - Message message = MessageBuilder.withPayload(createPerson("Bob", 35)).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - Person person = - this.r2dbcEntityTemplate.select(Person.class) - .first() - .block(); - - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.DELETE); - message = MessageBuilder.withPayload(person).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - this.personRepository.findAll() - .as(StepVerifier::create) - .expectNextCount(0) - .verifyComplete(); - } - - @Test - public void validateMessageHandlingWithDeleteQueryCollection() { - this.r2dbcMessageHandler.setValuesExpression(new FunctionExpression>(Message::getPayload)); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT); - this.r2dbcMessageHandler.setTableName("person"); - Map payload = new HashMap<>(); - payload.put("name", "Bob"); - payload.put("age", 35); - Message message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - payload = new HashMap<>(); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.DELETE); - - Object insertedId = client.sql("SELECT id FROM person") - .fetch() - .first() - .block() - .get("id"); - - this.r2dbcMessageHandler.setCriteriaExpression( - new FunctionExpression>((m) -> Criteria.where("id").is(insertedId))); - message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - client.sql("SELECT age,name FROM person where age=35") - .fetch().all() - .as(StepVerifier::create) - .expectNextCount(0) - .verifyComplete(); - - } - - @Test - public void validateMessageHandlingWithDeleteQueryCollection_MultipleRows() { - this.r2dbcMessageHandler.setValuesExpression(new FunctionExpression>(Message::getPayload)); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT); - this.r2dbcMessageHandler.setTableName("person"); - Map payload = new HashMap<>(); - payload.put("name", "Bob"); - payload.put("age", 35); - Message message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - payload = new HashMap<>(); - payload.put("name", "Rob"); - payload.put("age", 40); - message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - payload = new HashMap<>(); - this.r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.DELETE); - - Object insertedId = client.sql("SELECT id FROM person") - .fetch() - .first() - .block() - .get("id"); - - this.r2dbcMessageHandler.setCriteriaExpression( - new FunctionExpression>((m) -> Criteria.where("id").is(insertedId))); - message = MessageBuilder.withPayload(payload).build(); - waitFor(this.r2dbcMessageHandler.handleMessage(message)); - - client.sql("SELECT age,name FROM person where age=40") - .fetch().all() - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); - - } - - private Person createPerson(String bob, Integer age) { - return new Person(bob, age); - } - - private static T waitFor(Mono mono) { - return mono.block(Duration.ofSeconds(10)); - } - - @Configuration - @Import(R2dbcDatabaseConfiguration.class) - static class R2dbcMessageHandlerConfiguration { - - @Autowired - DatabaseClient databaseClient; - - @Bean - public R2dbcMessageHandler r2dbcMessageHandler(R2dbcEntityTemplate r2dbcEntityTemplate) { - return new R2dbcMessageHandler(r2dbcEntityTemplate); - } - - @Bean - public StandardEvaluationContext integrationEvaluationContext(ApplicationContext applicationContext) { - StandardEvaluationContext integrationEvaluationContext = new StandardEvaluationContext(); - integrationEvaluationContext.addPropertyAccessor(new MapAccessor()); - integrationEvaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext)); - return integrationEvaluationContext; - } - - } - -} - - diff --git a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/repository/PersonRepository.java b/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/repository/PersonRepository.java deleted file mode 100644 index 9e316185535..00000000000 --- a/spring-integration-r2dbc/src/test/java/org/springframework/integration/r2dbc/repository/PersonRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.r2dbc.repository; - -import org.springframework.data.repository.reactive.ReactiveCrudRepository; -import org.springframework.integration.r2dbc.entity.Person; - -/** - * @author Rohan Mukesh - * - * @since 5.4 - */ -public interface PersonRepository extends ReactiveCrudRepository { - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/channel/SubscribableRedisChannel.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/channel/SubscribableRedisChannel.java deleted file mode 100644 index 05b8f2e33be..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/channel/SubscribableRedisChannel.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.channel; - -import java.util.concurrent.Executor; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.MessageDispatchingException; -import org.springframework.integration.channel.AbstractMessageChannel; -import org.springframework.integration.channel.BroadcastCapableChannel; -import org.springframework.integration.channel.ChannelUtils; -import org.springframework.integration.dispatcher.BroadcastingDispatcher; -import org.springframework.integration.support.converter.SimpleMessageConverter; -import org.springframework.integration.support.management.ManageableSmartLifecycle; -import org.springframework.integration.util.ErrorHandlingTaskExecutor; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.util.Assert; -import org.springframework.util.ErrorHandler; -import org.springframework.util.StringUtils; - -/** - * An {@link AbstractMessageChannel} implementation with {@link BroadcastCapableChannel} - * aspect to provide a pub-sub semantics to consume messages fgrom Redis topic. - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -@SuppressWarnings("rawtypes") -public class SubscribableRedisChannel extends AbstractMessageChannel - implements BroadcastCapableChannel, ManageableSmartLifecycle { - - private final RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - - private final RedisConnectionFactory connectionFactory; - - private final RedisTemplate redisTemplate; - - private final String topicName; - - private @Nullable Executor taskExecutor; - - @SuppressWarnings("NullAway.Init") - private final BroadcastingDispatcher dispatcher = new BroadcastingDispatcher(true); - - private RedisSerializer serializer = new StringRedisSerializer(); - - private MessageConverter messageConverter = new SimpleMessageConverter(); - - private volatile @Nullable Integer maxSubscribers; - - private volatile boolean initialized; - - public SubscribableRedisChannel(RedisConnectionFactory connectionFactory, String topicName) { - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - Assert.hasText(topicName, "'topicName' must not be empty"); - this.connectionFactory = connectionFactory; - this.redisTemplate = new StringRedisTemplate(connectionFactory); - this.topicName = topicName; - } - - public void setTaskExecutor(Executor taskExecutor) { - Assert.notNull(taskExecutor, "'taskExecutor' must not be null"); - this.taskExecutor = taskExecutor; - } - - @Override - public void setMessageConverter(MessageConverter messageConverter) { - Assert.notNull(messageConverter, "'messageConverter' must not be null"); - this.messageConverter = messageConverter; - } - - public void setSerializer(RedisSerializer serializer) { - Assert.notNull(serializer, "'serializer' must not be null"); - this.serializer = serializer; - } - - /** - * Specify the maximum number of subscribers supported by the - * channel's dispatcher. - * @param maxSubscribers The maximum number of subscribers allowed. - */ - public void setMaxSubscribers(int maxSubscribers) { - this.maxSubscribers = maxSubscribers; - this.dispatcher.setMaxSubscribers(maxSubscribers); - } - - @Override - public boolean subscribe(MessageHandler handler) { - return this.dispatcher.addHandler(handler); - } - - @Override - public boolean unsubscribe(MessageHandler handler) { - return this.dispatcher.removeHandler(handler); - } - - @Override - protected boolean doSend(Message message, long arg1) { - Object value = this.messageConverter.fromMessage(message, Object.class); - this.redisTemplate.convertAndSend(this.topicName, value); // NOSONAR - null can be sent - return true; - } - - @Override - public void onInit() { - if (this.initialized) { - return; - } - super.onInit(); - if (this.maxSubscribers == null) { - setMaxSubscribers(getIntegrationProperties().getChannelsMaxBroadcastSubscribers()); - } - if (this.messageConverter == null) { - this.messageConverter = new SimpleMessageConverter(); - } - BeanFactory beanFactory = getBeanFactory(); - if (this.messageConverter instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.messageConverter).setBeanFactory(beanFactory); - } - this.container.setConnectionFactory(this.connectionFactory); - - if (this.taskExecutor == null) { - this.taskExecutor = new SimpleAsyncTaskExecutor(getBeanName() + "-"); - } - - if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) { - ErrorHandler errorHandler = ChannelUtils.getErrorHandler(beanFactory); - this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, errorHandler); - } - this.container.setTaskExecutor(this.taskExecutor); - MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageListenerDelegate()); - adapter.setSerializer(this.serializer); - adapter.afterPropertiesSet(); - this.container.addMessageListener(adapter, new ChannelTopic(this.topicName)); - this.container.afterPropertiesSet(); - this.dispatcher.setBeanFactory(beanFactory); - this.initialized = true; - } - - /* - * SmartLifecycle implementation (delegates to the MessageListener container) - */ - - @Override - public boolean isAutoStartup() { - return this.container.isAutoStartup(); - } - - @Override - public int getPhase() { - return this.container.getPhase(); - } - - @Override - public boolean isRunning() { - return this.container.isRunning(); - } - - @Override - public void start() { - this.container.start(); - } - - @Override - public void stop() { - this.container.stop(); - } - - @Override - public void stop(Runnable callback) { - this.container.stop(callback); - } - - @Override - public void destroy() { - try { - this.container.destroy(); - } - catch (Exception ex) { - throw new IllegalStateException("Cannot destroy " + this.container, ex); - } - } - - private class MessageListenerDelegate { - - MessageListenerDelegate() { - } - - @SuppressWarnings({"unused"}) - public void handleMessage(Object payload) { - Message siMessage = SubscribableRedisChannel.this.messageConverter.toMessage(payload, null); - if (siMessage == null) { - if (logger.isDebugEnabled()) { - logger.debug("MessageConverter returned null for payload: " + payload); - } - return; - } - try { - SubscribableRedisChannel.this.dispatcher.dispatch(siMessage); - } - catch (MessageDispatchingException e) { - String exceptionMessage = e.getMessage(); - throw new MessageDeliveryException(siMessage, - (exceptionMessage == null ? e.getClass().getSimpleName() : exceptionMessage) - + " for redis-channel '" - + (StringUtils.hasText(SubscribableRedisChannel.this.topicName) - ? SubscribableRedisChannel.this.topicName - : "unknown") - + "' (" + getFullChannelName() + ").", e); // NOSONAR false positive - never null - } - } - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/channel/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/channel/package-info.java deleted file mode 100644 index 53b5fd6c627..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/channel/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to Redis-backed channels. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.channel; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisChannelParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisChannelParser.java deleted file mode 100644 index 766b1e0bf3c..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisChannelParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractChannelParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.channel.SubscribableRedisChannel; -import org.springframework.util.StringUtils; - -/** - * Parser for the 'channel' and 'publish-subscribe-channel' elements of the - * Spring Integration Redis namespace. - * - * @author Oleg Zhurakusky - * @author Artem Bilan - * @author Gary Russell - * @since 2.1 - */ -public class RedisChannelParser extends AbstractChannelParser { - - @Override - protected BeanDefinitionBuilder buildBeanDefinition(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SubscribableRedisChannel.class); - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - String topicName = element.getAttribute("topic-name"); - builder.addConstructorArgValue(topicName); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer"); - // The following 2 attributes should be added once configurable on the RedisMessageListenerContainer - // IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase"); - // IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "max-subscribers"); - - return builder; - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java deleted file mode 100644 index 9e052b9793f..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.inbound.RedisInboundChannelAdapter; -import org.springframework.util.StringUtils; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Venil Noronha - * - * @since 2.1 - */ -public class RedisInboundChannelAdapterParser extends AbstractChannelAdapterParser { - - @Override - protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisInboundChannelAdapter.class); - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - builder.addPropertyReference("outputChannel", channelName); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "topics"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "topic-patterns"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer", true); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor"); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java deleted file mode 100644 index 728e9d3b576..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * Namespace handler for Spring Integration's 'redis' namespace. - * - * @author Oleg Zhurakousky - * @author Artem Bilan - * @since 2.1 - */ -public class RedisNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - public void init() { - registerBeanDefinitionParser("publish-subscribe-channel", new RedisChannelParser()); - registerBeanDefinitionParser("inbound-channel-adapter", new RedisInboundChannelAdapterParser()); - registerBeanDefinitionParser("store-inbound-channel-adapter", new RedisStoreInboundChannelAdapterParser()); - registerBeanDefinitionParser("store-outbound-channel-adapter", new RedisStoreOutboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-channel-adapter", new RedisOutboundChannelAdapterParser()); - registerBeanDefinitionParser("queue-inbound-channel-adapter", new RedisQueueInboundChannelAdapterParser()); - registerBeanDefinitionParser("queue-outbound-channel-adapter", new RedisQueueOutboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-gateway", new RedisOutboundGatewayParser()); - registerBeanDefinitionParser("queue-inbound-gateway", new RedisQueueInboundGatewayParser()); - registerBeanDefinitionParser("queue-outbound-gateway", new RedisQueueOutboundGatewayParser()); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java deleted file mode 100644 index 49a9c52dc71..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.outbound.RedisPublishingMessageHandler; -import org.springframework.util.StringUtils; - -/** - * Parser for the {@code } component. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Artem Bilan - * @since 2.1 - */ -public class RedisOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisPublishingMessageHandler.class); - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer"); - - BeanDefinition topicExpression = IntegrationNamespaceUtils - .createExpressionDefinitionFromValueOrExpression("topic", "topic-expression", parserContext, element, true); - builder.addPropertyValue("topicExpression", topicExpression); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundGatewayParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundGatewayParser.java deleted file mode 100644 index e4624914b39..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundGatewayParser.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.outbound.ExpressionArgumentsStrategy; -import org.springframework.integration.redis.outbound.RedisOutboundGateway; -import org.springframework.util.StringUtils; - -/** - * Parser for the {@code } component. - * - * @author Artem Bilan - * @since 4.0 - */ -public class RedisOutboundGatewayParser extends AbstractConsumerEndpointParser { - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisOutboundGateway.class); - - String redisTemplate = element.getAttribute("redis-template"); - String connectionFactory = element.getAttribute("connection-factory"); - if (StringUtils.hasText(redisTemplate) && StringUtils.hasText(connectionFactory)) { - parserContext.getReaderContext().error("Only one of '" + redisTemplate + "' or '" - + connectionFactory + "' is allowed.", element); - } - if (StringUtils.hasText(redisTemplate)) { - builder.addConstructorArgReference(redisTemplate); - } - else { - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - } - - String argumentExpressions = element.getAttribute("argument-expressions"); - boolean hasArgumentExpressions = StringUtils.hasText(argumentExpressions); - String argumentsStrategy = element.getAttribute("arguments-strategy"); - boolean hasArgumentStrategy = element.hasAttribute("arguments-strategy"); - - if (hasArgumentExpressions & hasArgumentStrategy) { - parserContext.getReaderContext() - .error("'argument-expressions' and 'arguments-strategy' are mutually exclusive.", element); - } - - if (hasArgumentExpressions) { - BeanDefinitionBuilder argumentsBuilder = - BeanDefinitionBuilder.genericBeanDefinition(ExpressionArgumentsStrategy.class) - .addConstructorArgValue(argumentExpressions) - .addConstructorArgValue(element.getAttribute("use-command-variable")); - builder.addPropertyValue("argumentsStrategy", argumentsBuilder.getBeanDefinition()); - } - else if (StringUtils.hasLength(argumentsStrategy)) { - builder.addPropertyReference("argumentsStrategy", argumentsStrategy); - } - else if (hasArgumentStrategy) { - builder.addPropertyValue("argumentsStrategy", null); - } - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout"); - BeanDefinition expressionDef = - IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("command-expression", element); - if (expressionDef != null) { - builder.addPropertyValue("commandExpression", expressionDef); - } - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "arguments-serializer"); - - return builder; - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParser.java deleted file mode 100644 index 47f62018053..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint; -import org.springframework.util.StringUtils; - -/** - * Parser for the <queue-inbound-channel-adapter> element of the 'redis' namespace. - * - * @author Artem Bilan - * @author Rainer Frey - * @since 3.0 - */ -public class RedisQueueInboundChannelAdapterParser extends AbstractChannelAdapterParser { - - @Override - protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisQueueMessageDrivenEndpoint.class); - builder.addConstructorArgValue(element.getAttribute("queue")); - - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer", true); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-message"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "receive-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "recovery-interval"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "right-pop"); - builder.addPropertyReference("outputChannel", channelName); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParser.java deleted file mode 100644 index 392df25c346..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParser.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.integration.config.xml.AbstractInboundGatewayParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.inbound.RedisQueueInboundGateway; -import org.springframework.util.StringUtils; - -/** - * Parser for the <queue-inbound-gateway> element of the 'redis' namespace. - * - * @author David Liu - * @author Artem Bilan - * @since 4.1 - */ -public class RedisQueueInboundGatewayParser extends AbstractInboundGatewayParser { - - @Override - protected Class getBeanClass(Element element) { - return RedisQueueInboundGateway.class; - } - - @Override - protected boolean isEligibleAttribute(String attributeName) { - return !attributeName.equals("queue") // NOSONAR boolean complexity - && !attributeName.equals("connection-factory") - && !attributeName.equals("serializer") - && !attributeName.equals("task-executor") - && super.isEligibleAttribute(attributeName); - } - - @Override - protected void doPostProcess(BeanDefinitionBuilder builder, Element element) { - builder.addConstructorArgValue(element.getAttribute("queue")); - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer", true); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "receive-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "recovery-interval"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor"); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParser.java deleted file mode 100644 index ede4798f07a..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParser.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter; -import org.springframework.util.StringUtils; - -/** - * Parser for the <int-redis:queue-outbound-channel-adapter> element. - * - * @author Artem Bilan - * @author Rainer Frey - * @since 3.0 - */ -public class RedisQueueOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisQueueOutboundChannelAdapter.class); - BeanDefinition queueExpression = IntegrationNamespaceUtils - .createExpressionDefinitionFromValueOrExpression("queue", "queue-expression", parserContext, element, true); - builder.addConstructorArgValue(queueExpression); - - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "left-push"); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParser.java deleted file mode 100644 index b2a621d588d..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParser.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.outbound.RedisQueueOutboundGateway; -import org.springframework.util.StringUtils; - -/** - * Parser for the <int-redis:queue-outbound-channel-adapter> element. - * - * @author Artem Bilan - * @author David Liu - * @since 3.0 - */ -public class RedisQueueOutboundGatewayParser extends AbstractConsumerEndpointParser { - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisQueueOutboundGateway.class); - builder.addConstructorArgValue(element.getAttribute("queue")); - String connectionFactory = element.getAttribute("connection-factory"); - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "receiveTimeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); - return builder; - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java deleted file mode 100644 index a1bda5d2c9b..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.inbound.RedisStoreMessageSource; -import org.springframework.util.StringUtils; - -/** - * Parser for Redis store inbound adapters - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @since 2.2 - */ -public class RedisStoreInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { - - @Override - protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RedisStoreMessageSource.class); - String redisTemplate = element.getAttribute("redis-template"); - String connectionFactory = element.getAttribute("connection-factory"); - if (StringUtils.hasText(redisTemplate) && StringUtils.hasText(connectionFactory)) { - parserContext.getReaderContext().error("Only one of '" + redisTemplate + "' or '" - + connectionFactory + "' is allowed.", element); - } - if (StringUtils.hasText(redisTemplate)) { - builder.addConstructorArgReference(redisTemplate); - } - else { - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - } - boolean atLeastOneRequired = true; - BeanDefinition expressionDef = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("key", "key-expression", - parserContext, element, atLeastOneRequired); - builder.addConstructorArgValue(expressionDef); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "collection-type"); - - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParser.java deleted file mode 100644 index 37d1bb7f56b..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParser.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.redis.outbound.RedisStoreWritingMessageHandler; -import org.springframework.util.StringUtils; - -/** - * Parser for the <redis:store-outbound-channel-adapter> element. - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.2 - */ -public class RedisStoreOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(RedisStoreWritingMessageHandler.class); - - String redisTemplateRef = element.getAttribute("redis-template"); - String connectionFactory = element.getAttribute("connection-factory"); - if (StringUtils.hasText(redisTemplateRef) && StringUtils.hasText(connectionFactory)) { - parserContext.getReaderContext().error("Only one of 'redis-template' or 'connection-factory'" + - " is allowed.", element); - } - if (StringUtils.hasText(redisTemplateRef)) { - builder.addConstructorArgReference(redisTemplateRef); - } - else { - if (!StringUtils.hasText(connectionFactory)) { - connectionFactory = "redisConnectionFactory"; - } - builder.addConstructorArgReference(connectionFactory); - } - - boolean hasKey = element.hasAttribute("key"); - boolean hasKeyExpression = element.hasAttribute("key-expression"); - if (hasKey && hasKeyExpression) { - parserContext.getReaderContext().error( - "At most one of 'key' or 'key-expression' is allowed.", element); - } - if (hasKey) { - builder.addPropertyValue("key", new TypedStringValue(element.getAttribute("key"))); - } - if (hasKeyExpression) { - builder.addPropertyValue("keyExpressionString", element.getAttribute("key-expression")); - } - - String mapKeyExpression = element.getAttribute("map-key-expression"); - if (StringUtils.hasText(mapKeyExpression)) { - builder.addPropertyValue("mapKeyExpressionString", mapKeyExpression); - } - - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "collection-type"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload-elements"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zset-increment-expression", - "zsetIncrementExpressionString"); - return builder.getBeanDefinition(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/package-info.java deleted file mode 100644 index 8193f58ef37..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for configuration - parsers, namespace handlers. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.config; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisExceptionEvent.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisExceptionEvent.java deleted file mode 100644 index 4b3a17380ef..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisExceptionEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.event; - -/** - * @author Artem Bilan - * @since 3.0 - */ -@SuppressWarnings("serial") -public class RedisExceptionEvent extends RedisIntegrationEvent { - - public RedisExceptionEvent(Object source, Throwable cause) { - super(source, cause); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisIntegrationEvent.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisIntegrationEvent.java deleted file mode 100644 index 11048cad1f5..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisIntegrationEvent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.event; - -import org.springframework.integration.events.IntegrationEvent; - -/** - * @author Artem Bilan - * - * @since 3.0 - * - */ -@SuppressWarnings("serial") -public abstract class RedisIntegrationEvent extends IntegrationEvent { - - public RedisIntegrationEvent(Object source) { - super(source); - } - - public RedisIntegrationEvent(Object source, Throwable cause) { - super(source, cause); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/package-info.java deleted file mode 100644 index 9eced036b7d..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Events generated by the redis module - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.event; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/ReactiveRedisStreamMessageProducer.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/ReactiveRedisStreamMessageProducer.java deleted file mode 100644 index 23da0dc1267..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/ReactiveRedisStreamMessageProducer.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.time.Duration; -import java.util.function.Function; - -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.connection.stream.Consumer; -import org.springframework.data.redis.connection.stream.ReadOffset; -import org.springframework.data.redis.connection.stream.Record; -import org.springframework.data.redis.connection.stream.StreamOffset; -import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.core.ReactiveStreamOperations; -import org.springframework.data.redis.hash.HashMapper; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.stream.StreamReceiver; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.acks.SimpleAcknowledgment; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.converter.MessageConversionException; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A {@link MessageProducerSupport} for reading messages from a Redis Stream and publishing them into the provided - * output channel. - * By default this adapter reads message as a standalone client {@code XREAD} (Redis command) but can be switched to a - * Consumer Group feature {@code XREADGROUP} by setting {@link #consumerName} field. - * By default the Consumer Group name is the id of this bean {@link #getBeanName()}. - * - * @author Attoumane Ahamadi - * @author Artem Bilan - * @author Rohan Mukesh - * - * @since 5.4 - */ - -public class ReactiveRedisStreamMessageProducer extends MessageProducerSupport { - - private final ReactiveRedisConnectionFactory reactiveConnectionFactory; - - private final String streamKey; - - @SuppressWarnings("this-escape") - private final StreamReceiver.StreamReceiverOptionsBuilder streamReceiverOptionsBuilder = - StreamReceiver.StreamReceiverOptions.builder() - .pollTimeout(Duration.ZERO) - .onErrorResume(this::handleReceiverError); - - @SuppressWarnings("NullAway.Init") - private ReactiveStreamOperations reactiveStreamOperations; - - private StreamReceiver.@Nullable StreamReceiverOptions streamReceiverOptions; - - @SuppressWarnings("NullAway.Init") - private StreamReceiver streamReceiver; - - private ReadOffset readOffset = ReadOffset.latest(); - - private boolean extractPayload = true; - - private boolean autoAck = true; - - @SuppressWarnings("NullAway.Init") - private String consumerGroup; - - private @Nullable String consumerName; - - private boolean createConsumerGroup; - - private boolean receiverBuilderOptionSet; - - public ReactiveRedisStreamMessageProducer(ReactiveRedisConnectionFactory reactiveConnectionFactory, - String streamKey) { - - Assert.notNull(reactiveConnectionFactory, "'connectionFactory' must not be null"); - Assert.hasText(streamKey, "'streamKey' must be set"); - this.reactiveConnectionFactory = reactiveConnectionFactory; - this.streamKey = streamKey; - } - - /** - * Define the offset from which we want to read message. By default the {@link ReadOffset#latest()} is used. - * {@link ReadOffset#latest()} is equal to '$', which is the Id used with {@code XREAD} to get new data added to - * the stream. Note that when switching to the Consumer Group feature, we set it to - * {@link ReadOffset#lastConsumed()} if it is still equal to {@link ReadOffset#latest()}. - * @param readOffset the desired offset - */ - public void setReadOffset(ReadOffset readOffset) { - this.readOffset = readOffset; - } - - /** - * Configure this channel adapter to extract or not value from the {@link Record}. - * @param extractPayload default true - */ - public void setExtractPayload(boolean extractPayload) { - this.extractPayload = extractPayload; - } - - /** - * Set whether or not acknowledge message read in the Consumer Group. {@code true} by default. - * @param autoAck the acknowledge option. - */ - public void setAutoAck(boolean autoAck) { - this.autoAck = autoAck; - } - - /** - * Set the name of the Consumer Group. It is possible to create that Consumer Group if desired, see: - * {@link #createConsumerGroup}. If not set, the defined bean name {@link #getBeanName()} is used. - * @param consumerGroup the Consumer Group on which this adapter should register to listen messages. - */ - public void setConsumerGroup(String consumerGroup) { - this.consumerGroup = consumerGroup; - } - - /** - * Set the name of the consumer. When a consumer name is provided, this adapter is switched to the Consumer Group - * feature. Note that this value should be unique in the group. - * @param consumerName the consumer name in the Consumer Group - */ - public void setConsumerName(@Nullable String consumerName) { - this.consumerName = consumerName; - } - - /** - * Create the Consumer Group if and only if it does not exist. - * During the creation we also create the stream, see {@code MKSTREAM}. - * @param createConsumerGroup specify if we should create the Consumer Group, {@code false} by default - */ - public void setCreateConsumerGroup(boolean createConsumerGroup) { - this.createConsumerGroup = createConsumerGroup; - } - - /** - * Set {@link ReactiveStreamOperations} used to customize the {@link StreamReceiver}. - * It provides a way to set the polling timeout and the serialization context. - * By default the polling timeout is set to infinite and - * {@link org.springframework.data.redis.serializer.StringRedisSerializer} is used. - * Mutually exclusive with 'pollTimeout', 'batchSize', 'onErrorResume', 'serializer', 'targetType', 'objectMapper'. - * @param streamReceiverOptions the desired receiver options - * */ - public void setStreamReceiverOptions( - StreamReceiver.@Nullable StreamReceiverOptions streamReceiverOptions) { - - Assert.isTrue(!this.receiverBuilderOptionSet, - "The 'streamReceiverOptions' is mutually exclusive with 'pollTimeout', 'batchSize', " + - "'onErrorResume', 'serializer', 'targetType', 'objectMapper'"); - this.streamReceiverOptions = streamReceiverOptions; - } - - private void assertStreamReceiverOptions(String property) { - Assert.isNull(this.streamReceiverOptions, - () -> "'" + property + "' cannot be set when 'StreamReceiver.StreamReceiverOptions' is provided."); - } - - /** - * Configure a poll timeout for the BLOCK option during reading. - * Mutually exclusive with {@link #setStreamReceiverOptions(StreamReceiver.StreamReceiverOptions)}. - * @param pollTimeout the timeout for polling. - * @since 5.5 - * @see org.springframework.data.redis.stream.StreamReceiver.StreamReceiverOptionsBuilder#pollTimeout(Duration) - */ - public void setPollTimeout(Duration pollTimeout) { - assertStreamReceiverOptions("pollTimeout"); - this.streamReceiverOptionsBuilder.pollTimeout(pollTimeout); - this.receiverBuilderOptionSet = true; - } - - /** - * Configure a batch size for the COUNT option during reading. - * Mutually exclusive with {@link #setStreamReceiverOptions(StreamReceiver.StreamReceiverOptions)}. - * @param recordsPerPoll must be greater zero. - * @since 5.5 - * @see org.springframework.data.redis.stream.StreamReceiver.StreamReceiverOptionsBuilder#batchSize(int) - */ - public void setBatchSize(int recordsPerPoll) { - assertStreamReceiverOptions("batchSize"); - this.streamReceiverOptionsBuilder.batchSize(recordsPerPoll); - this.receiverBuilderOptionSet = true; - } - - /** - * Configure a resume Function to resume the main sequence when polling the stream fails. - * Mutually exclusive with {@link #setStreamReceiverOptions(StreamReceiver.StreamReceiverOptions)}. - * By default this function extract the failed {@link Record} and sends an - * {@link org.springframework.messaging.support.ErrorMessage} to the provided {@link #setErrorChannel}. - * The failed message for this record may have a {@link IntegrationMessageHeaderAccessor#ACKNOWLEDGMENT_CALLBACK} - * header when manual acknowledgment is configured for this message producer. - * @param resumeFunction must not be null. - * @since 5.5 - * @see org.springframework.data.redis.stream.StreamReceiver.StreamReceiverOptionsBuilder#onErrorResume(Function) - */ - public void setOnErrorResume(Function> resumeFunction) { - assertStreamReceiverOptions("onErrorResume"); - this.streamReceiverOptionsBuilder.onErrorResume(resumeFunction); - this.receiverBuilderOptionSet = true; - } - - /** - * Configure a key, hash key and hash value serializer. - * Mutually exclusive with {@link #setStreamReceiverOptions(StreamReceiver.StreamReceiverOptions)}. - * @param pair must not be null. - * @since 5.5 - * @see StreamReceiver.StreamReceiverOptionsBuilder#serializer(RedisSerializationContext) - */ - public void setSerializer(RedisSerializationContext.SerializationPair pair) { - assertStreamReceiverOptions("serializer"); - this.streamReceiverOptionsBuilder.serializer(pair); - this.receiverBuilderOptionSet = true; - } - - /** - * Configure a hash target type. Changes the emitted Record type to ObjectRecord. - * Mutually exclusive with {@link #setStreamReceiverOptions(StreamReceiver.StreamReceiverOptions)}. - * @param targetType must not be null. - * @since 5.5 - * @see StreamReceiver.StreamReceiverOptionsBuilder#targetType(Class) - */ - public void setTargetType(Class targetType) { - assertStreamReceiverOptions("targetType"); - this.streamReceiverOptionsBuilder.targetType(targetType); - this.receiverBuilderOptionSet = true; - } - - /** - * Configure a hash mapper. - * Mutually exclusive with {@link #setStreamReceiverOptions(StreamReceiver.StreamReceiverOptions)}. - * @param hashMapper must not be null. - * @since 5.5 - * @see StreamReceiver.StreamReceiverOptionsBuilder#objectMapper(HashMapper) - */ - public void setObjectMapper(HashMapper hashMapper) { - assertStreamReceiverOptions("objectMapper"); - this.streamReceiverOptionsBuilder.objectMapper(hashMapper); - this.receiverBuilderOptionSet = true; - } - - @Override - public String getComponentType() { - return "redis:stream-inbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.streamReceiverOptions == null) { - this.streamReceiverOptions = this.streamReceiverOptionsBuilder.build(); - } - this.streamReceiver = StreamReceiver.create(this.reactiveConnectionFactory, this.streamReceiverOptions); - if (StringUtils.hasText(this.consumerName) && !StringUtils.hasText(this.consumerGroup)) { - this.consumerGroup = getBeanName(); - } - ReactiveRedisTemplate reactiveRedisTemplate = - new ReactiveRedisTemplate<>(this.reactiveConnectionFactory, RedisSerializationContext.string()); - this.reactiveStreamOperations = reactiveRedisTemplate.opsForStream(); - } - - @Override - protected void doStart() { - StreamOffset offset = StreamOffset.create(this.streamKey, this.readOffset); - - Flux> events; - - if (!StringUtils.hasText(this.consumerName)) { - events = this.streamReceiver.receive(offset); - } - else { - Mono consumerGroupMono = Mono.empty(); - if (this.createConsumerGroup) { - consumerGroupMono = - this.reactiveStreamOperations.createGroup(this.streamKey, this.consumerGroup) - .onErrorReturn(this.consumerGroup); - } - - Consumer consumer = Consumer.from(this.consumerGroup, this.consumerName); - - if (offset.getOffset().equals(ReadOffset.latest())) { - // for consumer group offset id should be equal to '>' - offset = StreamOffset.create(this.streamKey, ReadOffset.lastConsumed()); - } - - events = this.autoAck - ? this.streamReceiver.receiveAutoAck(consumer, offset) - : this.streamReceiver.receive(consumer, offset); - - events = consumerGroupMono.thenMany(events); - } - - Flux> messageFlux = - events.map((record) -> buildMessageFromRecord(record, this.extractPayload)); - subscribeToPublisher(messageFlux); - } - - private Message buildMessageFromRecord(Record record, boolean extractPayload) { - AbstractIntegrationMessageBuilder builder = - getMessageBuilderFactory() - .withPayload(extractPayload ? record.getValue() : record) - .setHeader(RedisHeaders.STREAM_KEY, record.getStream()) - .setHeader(RedisHeaders.STREAM_MESSAGE_ID, record.getId()) - .setHeader(RedisHeaders.CONSUMER_GROUP, this.consumerGroup) - .setHeader(RedisHeaders.CONSUMER, this.consumerName); - - if (!this.autoAck && this.consumerGroup != null) { - builder.setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, - (SimpleAcknowledgment) () -> - this.reactiveStreamOperations - .acknowledge(this.consumerGroup, record) - .subscribe()); - } - - return builder.build(); - } - - private Publisher handleReceiverError(Throwable error) { - Message failedMessage = null; - if (error instanceof ConversionFailedException) { - @SuppressWarnings("unchecked") - Record record = (Record) ((ConversionFailedException) error).getValue(); - if (record != null) { - failedMessage = buildMessageFromRecord(record, false); - } - } - MessagingException conversionException = (failedMessage != null) - ? new MessageConversionException(failedMessage, "Cannot deserialize Redis Stream Record", error) - : new MessageConversionException("Cannot deserialize Redis Stream Record", error); - if (!sendErrorMessageIfNecessary(null, conversionException)) { - logger.getLog().error(conversionException); - } - return Mono.empty(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java deleted file mode 100644 index ae46e0a524e..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Executor; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.PatternTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.listener.Topic; -import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.messaging.converter.SimpleMessageConverter; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Venil Noronha - * - * @since 2.1 - */ -public class RedisInboundChannelAdapter extends MessageProducerSupport { - - private final RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - - private volatile MessageConverter messageConverter = new SimpleMessageConverter(); - - @SuppressWarnings("NullAway.Init") - private volatile String[] topics; - - @SuppressWarnings("NullAway.Init") - private volatile String[] topicPatterns; - - private volatile RedisSerializer serializer = new StringRedisSerializer(); - - public RedisInboundChannelAdapter(RedisConnectionFactory connectionFactory) { - Assert.notNull(connectionFactory, "connectionFactory must not be null"); - this.container.setConnectionFactory(connectionFactory); - } - - public void setSerializer(RedisSerializer serializer) { - this.serializer = serializer; - } - - public void setTopics(String... topics) { - Assert.notEmpty(topics, "at least one topic is required"); - this.topics = Arrays.copyOf(topics, topics.length); - } - - public void setTopicPatterns(String... topicPatterns) { - Assert.notEmpty(topicPatterns, "at least one topic pattern is required"); - this.topicPatterns = Arrays.copyOf(topicPatterns, topicPatterns.length); - } - - public void setMessageConverter(MessageConverter messageConverter) { - Assert.notNull(messageConverter, "messageConverter must not be null"); - this.messageConverter = messageConverter; - } - - /** - * Specify an {@link Executor} used for running the message listeners when messages are received. - * @param taskExecutor the Executor to use for listener container. - * @since 4.3.13 - * @see RedisMessageListenerContainer#setTaskExecutor(Executor) - */ - public void setTaskExecutor(Executor taskExecutor) { - this.container.setTaskExecutor(taskExecutor); - } - - @Override - public String getComponentType() { - return "redis:inbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - boolean hasTopics = false; - if (this.topics != null) { - Assert.noNullElements(this.topics, "'topics' may not contain null elements."); - hasTopics = true; - } - boolean hasPatterns = false; - if (this.topicPatterns != null) { - Assert.noNullElements(this.topicPatterns, "'topicPatterns' may not contain null elements."); - hasPatterns = true; - - } - Assert.state(hasTopics || hasPatterns, "at least one topic or topic pattern is required for subscription."); - - if (this.messageConverter instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.messageConverter).setBeanFactory(getBeanFactory()); - } - MessageListenerDelegate delegate = new MessageListenerDelegate(); - MessageListenerAdapter adapter = new MessageListenerAdapter(delegate); - adapter.setSerializer(this.serializer); - List topicList = new ArrayList<>(); - if (hasTopics) { - for (String topic : this.topics) { - topicList.add(new ChannelTopic(topic)); - } - } - if (hasPatterns) { - for (String pattern : this.topicPatterns) { - topicList.add(new PatternTopic(pattern)); - } - } - adapter.afterPropertiesSet(); - this.container.addMessageListener(adapter, topicList); - if (!this.container.isActive()) { - this.container.afterPropertiesSet(); - } - } - - @Override - protected void doStart() { - this.container.start(); - } - - @Override - protected void doStop() { - this.container.stop(); - } - - private @Nullable Message convertMessage(Object object, String source) { - MessageHeaders messageHeaders = null; - if (StringUtils.hasText(source)) { - messageHeaders = new MessageHeaders(Collections.singletonMap(RedisHeaders.MESSAGE_SOURCE, source)); - } - - return this.messageConverter.toMessage(object, messageHeaders); - } - - private class MessageListenerDelegate { - - MessageListenerDelegate() { - } - - @SuppressWarnings("unused") - public void handleMessage(Object message, String source) { - sendMessage(convertMessage(message, source)); - } - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueInboundGateway.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueInboundGateway.java deleted file mode 100644 index e553d2bde4b..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueInboundGateway.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundListOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.channel.MessagePublishingErrorHandler; -import org.springframework.integration.gateway.MessagingGatewaySupport; -import org.springframework.integration.redis.event.RedisExceptionEvent; -import org.springframework.integration.support.management.IntegrationManagedResource; -import org.springframework.integration.util.ErrorHandlingTaskExecutor; -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.scheduling.SchedulingAwareRunnable; -import org.springframework.util.Assert; - -/** - * @author David Liu - * @author Artem Bilan - * @author Gary Russell - * @author Matthias Jeschke - * - * @since 4.1 - */ -@ManagedResource -@IntegrationManagedResource -public class RedisQueueInboundGateway extends MessagingGatewaySupport - implements ApplicationEventPublisherAware, BeanClassLoaderAware { - - private static final String QUEUE_NAME_SUFFIX = ".reply"; - - public static final long DEFAULT_RECEIVE_TIMEOUT = 1000; - - public static final long DEFAULT_RECOVERY_INTERVAL = 5000; - - private final RedisTemplate template; - - private final BoundListOperations boundListOperations; - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - private boolean serializerExplicitlySet; - - @SuppressWarnings("NullAway.Init") - private Executor taskExecutor; - - private @Nullable RedisSerializer serializer; - - private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT; - - private long recoveryInterval = DEFAULT_RECOVERY_INTERVAL; - - private boolean extractPayload = true; - - private volatile boolean listening; - - private volatile @Nullable Runnable stopCallback; - - /** - * @param queueName Must not be an empty String - * @param connectionFactory Must not be null - */ - public RedisQueueInboundGateway(String queueName, RedisConnectionFactory connectionFactory) { - Assert.hasText(queueName, "'queueName' is required"); - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - this.template = new RedisTemplate<>(); - this.template.setConnectionFactory(connectionFactory); - this.template.setEnableDefaultSerializer(false); - this.template.setKeySerializer(new StringRedisSerializer()); - this.template.afterPropertiesSet(); - this.boundListOperations = this.template.boundListOps(queueName); - } - - public void setExtractPayload(boolean extractPayload) { - this.extractPayload = extractPayload; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - public void setBeanClassLoader(ClassLoader beanClassLoader) { - if (!this.serializerExplicitlySet) { - this.serializer = new JdkSerializationRedisSerializer(beanClassLoader); - } - } - - public void setSerializer(RedisSerializer serializer) { - this.serializer = serializer; - this.serializerExplicitlySet = true; - } - - /** - * This timeout (milliseconds) is used when retrieving elements from the queue - * specified by {@link #boundListOperations}. - *

If the queue does contain elements, the data is retrieved immediately. However, - * if the queue is empty, the Redis connection is blocked until either an element - * can be retrieved from the queue or until the specified timeout passes. - *

A timeout of zero can be used to block indefinitely. If not set explicitly - * the timeout value will default to {@code 1000} - *

See also: https://redis.io/commands/brpop - * @param receiveTimeout Must be non-negative. Specified in milliseconds. - */ - public void setReceiveTimeout(long receiveTimeout) { - Assert.isTrue(receiveTimeout >= 0, "'receiveTimeout' must be >= 0."); - this.receiveTimeout = receiveTimeout; - } - - public void setTaskExecutor(Executor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - public void setRecoveryInterval(long recoveryInterval) { - this.recoveryInterval = recoveryInterval; - } - - @Override - protected void onInit() { - super.onInit(); - if (!this.extractPayload) { - Assert.notNull(this.serializer, "'serializer' has to be provided where 'extractPayload == false'."); - } - if (this.taskExecutor == null) { - String beanName = getComponentName(); - this.taskExecutor = new SimpleAsyncTaskExecutor((beanName == null ? "" : beanName + "-") - + getComponentType()); - } - Executor executor = this.taskExecutor; - if (!(executor instanceof ErrorHandlingTaskExecutor) && getBeanFactory() != null) { - MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler(); - errorHandler.setBeanFactory(getBeanFactory()); - if (getErrorChannel() != null) { - errorHandler.setDefaultErrorChannel(getErrorChannel()); - } - this.taskExecutor = new ErrorHandlingTaskExecutor(executor, errorHandler); - } - } - - @Override - public String getComponentType() { - return "redis:queue-inbound-gateway"; - } - - private void handlePopException(Exception e) { - this.listening = false; - if (isActive()) { - logger.error(e, () -> - "Failed to execute listening task. Will attempt to resubmit in " + this.recoveryInterval - + " milliseconds."); - publishException(e); - sleepBeforeRecoveryAttempt(); - } - else { - logger.debug(() -> "Failed to execute listening task. " + e.getClass() + ": " + e.getMessage()); - } - } - - private void receiveAndReply() { - byte[] value; - try { - value = this.boundListOperations.rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS); - } - catch (Exception e) { - handlePopException(e); - return; - } - String uuid; - if (value != null) { - if (!isActive()) { - this.boundListOperations.rightPush(value); - return; - } - uuid = StringRedisSerializer.UTF_8.deserialize(value); - if (uuid == null) { - return; - } - try { - value = this.template.boundListOps(uuid).rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS); - } - catch (Exception e) { - handlePopException(e); - return; - } - if (value != null) { - getRequestSendAndProduceReply(value, uuid); - } - } - } - - @SuppressWarnings("unchecked") - private void getRequestSendAndProduceReply(byte[] value, String uuid) { - if (!isActive()) { - this.template.boundListOps(uuid).rightPush(value); - byte[] serialized = StringRedisSerializer.UTF_8.serialize(uuid); - if (serialized != null) { - this.boundListOperations.rightPush(serialized); - } - return; - } - - Message requestMessage = prepareRequestMessage(value); - - if (requestMessage != null) { - Message replyMessage = sendAndReceiveMessage(requestMessage); - if (replyMessage != null) { - @Nullable byte[] replyPayload = null; - if (this.extractPayload) { - replyPayload = extractReplyPayload(replyMessage); - } - else { - if (this.serializer != null) { - replyPayload = ((RedisSerializer) this.serializer).serialize(replyMessage); - } - } - if (replyPayload != null) { - this.template.boundListOps(uuid + QUEUE_NAME_SUFFIX).leftPush(replyPayload); - } - } - } - } - - @Nullable - @SuppressWarnings("unchecked") - private Message prepareRequestMessage(byte[] value) { - Message requestMessage = null; - if (this.extractPayload) { - Object payload = value; - if (this.serializer != null) { - payload = this.serializer.deserialize(value); - if (payload == null) { - return null; - } - } - requestMessage = getMessageBuilderFactory().withPayload(payload).build(); - } - else { - try { - if (this.serializer != null) { - requestMessage = (Message) this.serializer.deserialize(value); - if (requestMessage == null) { - return null; - } - } - } - catch (Exception e) { - throw new MessagingException("Deserialization of Message failed.", e); - } - } - return requestMessage; - } - - @SuppressWarnings("unchecked") - private byte @Nullable [] extractReplyPayload(Message replyMessage) { - byte[] value = null; - if (!(replyMessage.getPayload() instanceof byte[])) { - if (replyMessage.getPayload() instanceof String && !this.serializerExplicitlySet) { - value = StringRedisSerializer.UTF_8.serialize((String) replyMessage.getPayload()); - } - else { - RedisSerializer serializer = this.serializer; - if (serializer != null) { - value = ((RedisSerializer) serializer).serialize(replyMessage.getPayload()); - } - } - } - else { - value = (byte[]) replyMessage.getPayload(); - } - return value; - } - - @Override - protected void doStart() { - super.doStart(); - restart(); - } - - /** - * Sleep according to the specified recovery interval. - * Called between recovery attempts. - */ - private void sleepBeforeRecoveryAttempt() { - if (this.recoveryInterval > 0) { - try { - Thread.sleep(this.recoveryInterval); - } - catch (InterruptedException e) { - logger.debug("Thread interrupted while sleeping the recovery interval"); - Thread.currentThread().interrupt(); - } - } - } - - private void publishException(Exception e) { - this.applicationEventPublisher.publishEvent(new RedisExceptionEvent(this, e)); - } - - private void restart() { - this.taskExecutor.execute(new ListenerTask()); - } - - @Override - protected void doStop(Runnable callback) { - this.stopCallback = callback; - doStop(); - } - - @Override - protected void doStop() { - super.doStop(); - this.listening = false; - } - - public boolean isListening() { - return this.listening; - } - - /** - * Returns the size of the Queue specified by {@link #boundListOperations}. The queue is - * represented by a Redis list. If the queue does not exist 0 - * is returned. See also LLEN - * @return Size of the queue. Never negative. - */ - @ManagedMetric - public long getQueueSize() { - Long size = this.boundListOperations.size(); - return size == null ? 0 : size; - } - - /** - * Clear the Redis Queue specified by {@link #boundListOperations}. - */ - @ManagedOperation - public void clearQueue() { - this.boundListOperations.getOperations().delete(this.boundListOperations.getKey()); - } - - private class ListenerTask implements SchedulingAwareRunnable { - - ListenerTask() { - } - - @Override - public boolean isLongLived() { - return true; - } - - @Override - public void run() { - try { - while (isActive()) { - RedisQueueInboundGateway.this.listening = true; - receiveAndReply(); - } - } - finally { - if (isActive()) { - restart(); - } - else { - Runnable callback = RedisQueueInboundGateway.this.stopCallback; - if (callback != null) { - callback.run(); - RedisQueueInboundGateway.this.stopCallback = null; - } - } - } - } - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java deleted file mode 100644 index 18733698503..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.util.Optional; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundListOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.channel.MessagePublishingErrorHandler; -import org.springframework.integration.endpoint.MessageProducerSupport; -import org.springframework.integration.redis.event.RedisExceptionEvent; -import org.springframework.integration.support.channel.ChannelResolverUtils; -import org.springframework.integration.support.management.IntegrationManagedResource; -import org.springframework.integration.util.ErrorHandlingTaskExecutor; -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.scheduling.SchedulingAwareRunnable; -import org.springframework.util.Assert; - -/** - * @author Mark Fisher - * @author Gunnar Hillert - * @author Artem Bilan - * @author Gary Russell - * @author Rainer Frey - * @author Matthias Jeschke - * - * @since 3.0 - */ -@ManagedResource -@IntegrationManagedResource -public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport - implements ApplicationEventPublisherAware, BeanClassLoaderAware { - - public static final long DEFAULT_RECEIVE_TIMEOUT = 1000; - - public static final long DEFAULT_RECOVERY_INTERVAL = 5000; - - private final BoundListOperations boundListOperations; - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - @SuppressWarnings("NullAway.Init") - private Executor taskExecutor; - - private @Nullable RedisSerializer serializer; - - private boolean serializerExplicitlySet; - - private boolean expectMessage = false; - - private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT; - - private long recoveryInterval = DEFAULT_RECOVERY_INTERVAL; - - private boolean rightPop = true; - - private volatile boolean listening; - - private volatile @Nullable Runnable stopCallback; - - /** - * @param queueName Must not be an empty String - * @param connectionFactory Must not be null - */ - public RedisQueueMessageDrivenEndpoint(String queueName, RedisConnectionFactory connectionFactory) { - Assert.hasText(queueName, "'queueName' is required"); - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - template.setEnableDefaultSerializer(false); - template.setKeySerializer(new StringRedisSerializer()); - template.afterPropertiesSet(); - this.boundListOperations = template.boundListOps(queueName); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - public void setBeanClassLoader(ClassLoader beanClassLoader) { - if (!this.serializerExplicitlySet) { - this.serializer = new JdkSerializationRedisSerializer(beanClassLoader); - } - } - - public void setSerializer(RedisSerializer serializer) { - this.serializer = serializer; - this.serializerExplicitlySet = true; - } - - /** - * When data is retrieved from the Redis queue, does the returned data represent - * just the payload for a Message, or does the data represent a serialized - * {@link Message}?. {@code expectMessage} defaults to false. This means - * the retrieved data will be used as the payload for a new Spring Integration - * Message. Otherwise, the data is deserialized as Spring Integration Message. - * @param expectMessage Defaults to false - */ - public void setExpectMessage(boolean expectMessage) { - this.expectMessage = expectMessage; - } - - /** - * This timeout (milliseconds) is used when retrieving elements from the queue - * specified by {@link #boundListOperations}. - *

If the queue does contain elements, the data is retrieved immediately. However, - * if the queue is empty, the Redis connection is blocked until either an element - * can be retrieved from the queue or until the specified timeout passes. - *

A timeout of zero can be used to block indefinitely. If not set explicitly - * the timeout value will default to {@code 1000} - *

See also: https://redis.io/commands/brpop - * @param receiveTimeout Must be non-negative. Specified in milliseconds. - */ - public void setReceiveTimeout(long receiveTimeout) { - Assert.isTrue(receiveTimeout >= 0, "'receiveTimeout' must be >= 0."); - this.receiveTimeout = receiveTimeout; - } - - public void setTaskExecutor(Executor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - public void setRecoveryInterval(long recoveryInterval) { - this.recoveryInterval = recoveryInterval; - } - - /** - * Specify if {@code POP} operation from Redis List should be {@code BRPOP} or {@code BLPOP}. - * @param rightPop the {@code BRPOP} flag. Defaults to {@code true}. - * @since 4.3 - */ - public void setRightPop(boolean rightPop) { - this.rightPop = rightPop; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.expectMessage) { - Assert.notNull(this.serializer, "'serializer' has to be provided where 'expectMessage == true'."); - } - if (this.taskExecutor == null) { - String beanName = getComponentName(); - this.taskExecutor = new SimpleAsyncTaskExecutor((beanName == null ? "" : beanName + "-") - + getComponentType()); - } - BeanFactory beanFactory = getBeanFactory(); - if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) { - MessagePublishingErrorHandler errorHandler = - new MessagePublishingErrorHandler(ChannelResolverUtils.getChannelResolver(beanFactory)); - if (getErrorChannel() != null) { - errorHandler.setDefaultErrorChannel(getErrorChannel()); - } - this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, errorHandler); - } - } - - @Override - public String getComponentType() { - return "redis:queue-inbound-channel-adapter"; - } - - @SuppressWarnings("unchecked") - private void popMessageAndSend() { - byte[] value = popForValue(); - - Message message = null; - - if (value != null) { - if (this.expectMessage) { - try { - if (this.serializer != null) { - message = (Message) this.serializer.deserialize(value); - } - } - catch (Exception e) { - throw new MessagingException("Deserialization of Message failed.", e); - } - } - else { - Object payload = value; - if (this.serializer != null) { - payload = this.serializer.deserialize(value); - } - if (payload != null) { - message = getMessageBuilderFactory().withPayload(payload).build(); - } - } - } - - if (message != null) { - if (this.listening) { - this.sendMessage(message); - } - else { - if (this.rightPop) { - this.boundListOperations.rightPush(value); - } - else { - this.boundListOperations.leftPush(value); - } - } - } - } - - private byte @Nullable [] popForValue() { - byte[] value = null; - try { - if (this.rightPop) { - value = this.boundListOperations.rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS); - } - else { - value = this.boundListOperations.leftPop(this.receiveTimeout, TimeUnit.MILLISECONDS); - } - } - catch (Exception ex) { - this.listening = false; - if (isActive()) { - logger.error(ex, - "Failed to execute listening task. Will attempt to resubmit in " + this.recoveryInterval - + " milliseconds."); - publishException(ex); - sleepBeforeRecoveryAttempt(); - } - else { - logger.debug(() -> "Failed to execute listening task. " + ex.getClass() + ": " + ex.getMessage()); - } - } - return value; - } - - @Override - protected void doStart() { - restart(); - } - - /** - * Sleep according to the specified recovery interval. - * Called between recovery attempts. - */ - private void sleepBeforeRecoveryAttempt() { - if (this.recoveryInterval > 0) { - try { - Thread.sleep(this.recoveryInterval); - } - catch (InterruptedException e) { - logger.debug("Thread interrupted while sleeping the recovery interval"); - Thread.currentThread().interrupt(); - } - } - } - - private void publishException(Exception e) { - if (this.applicationEventPublisher != null) { - this.applicationEventPublisher.publishEvent(new RedisExceptionEvent(this, e)); - } - else { - logger.debug(() -> "No application event publisher for exception: " + e.getMessage()); - } - } - - private void restart() { - this.taskExecutor.execute(new ListenerTask()); - } - - @Override - protected void doStop(Runnable callback) { - this.stopCallback = callback; - doStop(); - } - - @Override - protected void doStop() { - super.doStop(); - this.listening = false; - } - - public boolean isListening() { - return this.listening; - } - - /** - * Returns the size of the Queue specified by {@link #boundListOperations}. The queue is - * represented by a Redis list. If the queue does not exist 0 - * is returned. See also https://redis.io/commands/llen - * @return Size of the queue. Never negative. - */ - @ManagedMetric - public long getQueueSize() { - return Optional.ofNullable(this.boundListOperations.size()) - .orElse(0L); - } - - /** - * Clear the Redis Queue specified by {@link #boundListOperations}. - */ - @ManagedOperation - public void clearQueue() { - this.boundListOperations.getOperations().delete(this.boundListOperations.getKey()); - } - - private class ListenerTask implements SchedulingAwareRunnable { - - ListenerTask() { - } - - @Override - public boolean isLongLived() { - return true; - } - - @Override - public void run() { - try { - while (isActive()) { - RedisQueueMessageDrivenEndpoint.this.listening = true; - popMessageAndSend(); - } - } - finally { - if (isActive()) { - restart(); - } - else { - Runnable callback = RedisQueueMessageDrivenEndpoint.this.stopCallback; - if (callback != null) { - callback.run(); - RedisQueueMessageDrivenEndpoint.this.stopCallback = null; - } - } - } - } - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisStoreMessageSource.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisStoreMessageSource.java deleted file mode 100644 index 0b391e9a1df..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisStoreMessageSource.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.util.Collection; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.support.collections.RedisCollectionFactoryBean; -import org.springframework.data.redis.support.collections.RedisCollectionFactoryBean.CollectionType; -import org.springframework.data.redis.support.collections.RedisStore; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.endpoint.AbstractMessageSource; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.transaction.IntegrationResourceHolder; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; - -/** - * Inbound channel adapter which returns a Message representing a view into - * a Redis store. The type of store depends on the {@link #collectionType} attribute. - * Default is LIST. This adapter supports 5 types of collections identified by - * {@link CollectionType} - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.2 - */ -public class RedisStoreMessageSource extends AbstractMessageSource { - - private final RedisTemplate redisTemplate; - - private final Expression keyExpression; - - @SuppressWarnings("NullAway.Init") - private StandardEvaluationContext evaluationContext; - - private CollectionType collectionType = CollectionType.LIST; - - /** - * Create an instance with the provided {@link RedisTemplate} and SpEL expression - * which should resolve to a 'key' name of the collection to be used. - * It assumes that {@link RedisTemplate} is fully initialized and ready to be used. - * The 'keyExpression' will be evaluated on every call to the {@link #receive()} method. - * @param redisTemplate The Redis template. - * @param keyExpression The key expression. - */ - public RedisStoreMessageSource(RedisTemplate redisTemplate, Expression keyExpression) { - Assert.notNull(keyExpression, "'keyExpression' must not be null"); - Assert.notNull(redisTemplate, "'redisTemplate' must not be null"); - - this.redisTemplate = redisTemplate; - this.keyExpression = keyExpression; - } - - /** - * Create an instance with the provided {@link RedisConnectionFactory} and SpEL expression - * which should resolve to a 'key' name of the collection to be used. - * It will create and initialize an instance of {@link StringRedisTemplate} that uses - * {@link org.springframework.data.redis.serializer.StringRedisSerializer} for all - * serialization. - * The 'keyExpression' will be evaluated on every call to the {@link #receive()} method. - * @param connectionFactory The connection factory. - * @param keyExpression The key expression. - */ - public RedisStoreMessageSource(RedisConnectionFactory connectionFactory, Expression keyExpression) { - Assert.notNull(keyExpression, "'keyExpression' must not be null"); - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - - this.redisTemplate = new StringRedisTemplate(); - this.redisTemplate.setConnectionFactory(connectionFactory); - this.redisTemplate.afterPropertiesSet(); - - this.keyExpression = keyExpression; - } - - public void setCollectionType(CollectionType collectionType) { - this.collectionType = collectionType; - } - - @Override - protected void onInit() { - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - } - - /** - * Return a Message with the view into a {@link RedisStore} identified - * by {@link #keyExpression} - */ - @Override - protected @Nullable RedisStore doReceive() { - String key = this.keyExpression.getValue(this.evaluationContext, String.class); - Assert.hasText(key, "Failed to determine the key for the collection"); - - RedisStore store = createStoreView(key); - - Object holder = TransactionSynchronizationManager.getResource(this); - if (holder != null) { - Assert.isInstanceOf(IntegrationResourceHolder.class, holder); - ((IntegrationResourceHolder) holder).addAttribute("store", store); - } - - if (store instanceof Collection && ((Collection) store).size() < 1) { - return null; - } - else { - return store; - } - } - - private RedisStore createStoreView(String key) { - RedisCollectionFactoryBean fb = new RedisCollectionFactoryBean(); - fb.setKey(key); - fb.setTemplate(this.redisTemplate); - fb.setType(this.collectionType); - fb.afterPropertiesSet(); - return fb.getObject(); - } - - @Override - public String getComponentType() { - return "redis:store-inbound-channel-adapter"; - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/package-info.java deleted file mode 100644 index f8e49eab7f0..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting inbound endpoints. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.inbound; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/RedisMetadataStore.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/RedisMetadataStore.java deleted file mode 100644 index 2c8b341f5c1..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/RedisMetadataStore.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.metadata; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundHashOperations; -import org.springframework.data.redis.core.RedisOperations; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.support.collections.RedisProperties; -import org.springframework.integration.metadata.ConcurrentMetadataStore; -import org.springframework.util.Assert; - -/** - * Redis implementation of {@link ConcurrentMetadataStore}. Use this - * {@link ConcurrentMetadataStore} to achieve meta-data persistence across application - * restarts. - *

- * This implementation is based on the {@link RedisProperties} and its - * {@link #replace(String, String, String)}; it can't currently be used with a Redis - * cluster because the {@code WATCH} command is not supported. - * - * @author Gunnar Hillert - * @author Artem Bilan - * - * @since 3.0 - */ -public class RedisMetadataStore implements ConcurrentMetadataStore { - - private static final String KEY_MUST_NOT_BE_NULL = "'key' must not be null."; - - public static final String KEY = "MetaData"; - - private final RedisProperties properties; - - /** - * Specifies the {@link RedisProperties} backend for this {@link ConcurrentMetadataStore}. - * - * @param properties The properties. - */ - public RedisMetadataStore(RedisProperties properties) { - Assert.notNull(properties, "'properties' must not be null."); - this.properties = properties; - } - - /** - * Initializes the {@link RedisProperties} by provided {@link RedisConnectionFactory} - * and default hash key - {@link #KEY}. - * - * @param connectionFactory The connection factory. - */ - public RedisMetadataStore(RedisConnectionFactory connectionFactory) { - this(connectionFactory, KEY); - } - - /** - * Initializes the {@link RedisProperties} by provided {@link RedisConnectionFactory} and key. - * - * @param connectionFactory The connection factory. - * @param key The key. - */ - public RedisMetadataStore(RedisConnectionFactory connectionFactory, String key) { - Assert.notNull(connectionFactory, "'connectionFactory' must not be null."); - Assert.hasText(key, "'key' must not be empty."); - RedisOperations redisTemplate = new StringRedisTemplate(connectionFactory); - BoundHashOperations hashOperations = redisTemplate.boundHashOps(key); - this.properties = new RedisProperties(hashOperations); - } - - /** - * Initializes the {@link RedisProperties} by provided {@link RedisConnectionFactory} - * and default hash key - {@link #KEY}. - * - * @param operations The Redis operations object. - */ - public RedisMetadataStore(RedisOperations operations) { - this(operations, KEY); - } - - /** - * Initializes the {@link RedisProperties} by provided {@link RedisConnectionFactory} and key. - * - * @param operations The Redis operations object. - * @param key The key. - */ - public RedisMetadataStore(RedisOperations operations, String key) { - Assert.notNull(operations, "'operations' must not be null."); - Assert.hasText(key, "'key' must not be empty."); - BoundHashOperations hashOperations = operations.boundHashOps(key); - this.properties = new RedisProperties(hashOperations); - } - - /** - * Persists the provided key and value to Redis. - * - * @param key Must not be null - * @param value Must not be null - */ - @Override - public void put(String key, String value) { - Assert.notNull(key, KEY_MUST_NOT_BE_NULL); - Assert.notNull(value, "'value' must not be null."); - this.properties.put(key, value); - } - - /** - * Retrieve the persisted value for the provided key. - * - * @param key Must not be null - */ - @Override - public @Nullable String get(String key) { - Assert.notNull(key, KEY_MUST_NOT_BE_NULL); - Object value = this.properties.get(key); - if (value != null) { - Assert.isInstanceOf(String.class, value, "Invalid type in the store"); - } - return (String) value; - } - - @Override - public @Nullable String remove(String key) { - Assert.notNull(key, KEY_MUST_NOT_BE_NULL); - Object removed = this.properties.remove(key); - if (removed != null) { - Assert.isInstanceOf(String.class, removed, "The removed value was an invalid type"); - } - return (String) removed; - } - - @Override - public @Nullable String putIfAbsent(String key, String value) { - Assert.notNull(key, KEY_MUST_NOT_BE_NULL); - Assert.notNull(value, "'value' must not be null."); - Object oldValue = this.properties.putIfAbsent(key, value); - if (oldValue != null) { - Assert.isInstanceOf(String.class, oldValue, "Invalid type in the store"); - } - return (String) oldValue; - } - - @Override - public boolean replace(String key, String oldValue, String newValue) { - Assert.notNull(key, KEY_MUST_NOT_BE_NULL); - Assert.notNull(oldValue, "'oldValue' must not be null."); - Assert.notNull(newValue, "'newValue' must not be null."); - return this.properties.replace(key, oldValue, newValue); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/package-info.java deleted file mode 100644 index 501db5af010..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Provides support for Redis-based - * {@link org.springframework.integration.metadata.MetadataStore}s. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.metadata; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ArgumentsStrategy.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ArgumentsStrategy.java deleted file mode 100644 index 593b9ebd7e6..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ArgumentsStrategy.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import org.springframework.messaging.Message; - -/** - * @author Artem Bilan - * @since 4.0 - */ -@FunctionalInterface -public interface ArgumentsStrategy { - - Object[] resolve(String command, Message message); - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ExpressionArgumentsStrategy.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ExpressionArgumentsStrategy.java deleted file mode 100644 index 4502e41691b..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ExpressionArgumentsStrategy.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.0 - */ -public class ExpressionArgumentsStrategy implements ArgumentsStrategy, BeanFactoryAware, InitializingBean { - - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - - private final Expression[] argumentExpressions; - - private final boolean useCommandVariable; - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - public ExpressionArgumentsStrategy(String[] argumentExpressions) { - this(argumentExpressions, false); - } - - public ExpressionArgumentsStrategy(String[] argumentExpressions, boolean useCommandVariable) { - Assert.notNull(argumentExpressions, "'argumentExpressions' must not be null"); - Assert.noNullElements(argumentExpressions, "'argumentExpressions' cannot have null values."); - List expressions = new LinkedList<>(); - for (String argumentExpression : argumentExpressions) { - expressions.add(PARSER.parseExpression(argumentExpression)); - } - this.argumentExpressions = expressions.toArray(new Expression[0]); - this.useCommandVariable = useCommandVariable; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void afterPropertiesSet() { - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); - } - - @Override - public Object[] resolve(String command, Message message) { - EvaluationContext evaluationContextToUse = this.evaluationContext; - - if (this.useCommandVariable) { - evaluationContextToUse = IntegrationContextUtils.getEvaluationContext(this.beanFactory); - evaluationContextToUse.setVariable("cmd", command); - } - List arguments = new ArrayList<>(); - for (Expression argumentExpression : this.argumentExpressions) { - Object argument = argumentExpression.getValue(evaluationContextToUse, message); - if (argument != null) { - arguments.add(argument); - } - } - return arguments.toArray(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ReactiveRedisStreamMessageHandler.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ReactiveRedisStreamMessageHandler.java deleted file mode 100644 index 6ce059877a3..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/ReactiveRedisStreamMessageHandler.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.function.Function; - -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; - -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStreamCommands; -import org.springframework.data.redis.connection.stream.Record; -import org.springframework.data.redis.connection.stream.StreamRecords; -import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.core.ReactiveStreamOperations; -import org.springframework.data.redis.hash.HashMapper; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractReactiveMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Implementation of {@link org.springframework.messaging.ReactiveMessageHandler} which writes - * Message payload or Message itself (see {@link #extractPayload}) into a Redis stream using Reactive Stream operations. - * - * @author Attoumane Ahamadi - * @author Artem Bilan - * - * @since 5.4 - */ -public class ReactiveRedisStreamMessageHandler extends AbstractReactiveMessageHandler { - - private final Expression streamKeyExpression; - - private final ReactiveRedisConnectionFactory connectionFactory; - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - private boolean extractPayload = true; - - @SuppressWarnings("NullAway.Init") - private ReactiveStreamOperations reactiveStreamOperations; - - private RedisSerializationContext serializationContext = RedisSerializationContext.string(); - - private @Nullable HashMapper hashMapper; - - private @Nullable Function, RedisStreamCommands.XAddOptions> addOptionsFunction; - - /** - * Create an instance based on provided {@link ReactiveRedisConnectionFactory} and key for stream. - * @param connectionFactory the {@link ReactiveRedisConnectionFactory} to use - * @param streamKey the key for stream - */ - public ReactiveRedisStreamMessageHandler(ReactiveRedisConnectionFactory connectionFactory, String streamKey) { - this(connectionFactory, new LiteralExpression(streamKey)); - } - - /** - * Create an instance based on provided {@link ReactiveRedisConnectionFactory} and expression for stream key. - * @param connectionFactory the {@link ReactiveRedisConnectionFactory} to use - * @param streamKeyExpression the SpEL expression to evaluate a key for stream - */ - public ReactiveRedisStreamMessageHandler(ReactiveRedisConnectionFactory connectionFactory, - Expression streamKeyExpression) { - - Assert.notNull(streamKeyExpression, "'streamKeyExpression' must not be null"); - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - this.streamKeyExpression = streamKeyExpression; - this.connectionFactory = connectionFactory; - } - - public void setSerializationContext(RedisSerializationContext serializationContext) { - Assert.notNull(serializationContext, "'serializationContext' must not be null"); - this.serializationContext = serializationContext; - } - - /** - * (Optional) Set the {@link HashMapper} used to create {@link #reactiveStreamOperations}. - * The default {@link HashMapper} is defined from the provided {@link RedisSerializationContext} - * @param hashMapper the wanted hashMapper - * */ - public void setHashMapper(@Nullable HashMapper hashMapper) { - this.hashMapper = hashMapper; - } - - /** - * Set to {@code true} to extract the payload; otherwise - * the entire message is sent. Default {@code true}. - * @param extractPayload false to not extract. - */ - public void setExtractPayload(boolean extractPayload) { - this.extractPayload = extractPayload; - } - - /** - * Set a function to create a {@link RedisStreamCommands.XAddOptions} based on the request message. - * Cannot be null and cannot return null. - * @param addOptionsFunction the function to provide a {@link RedisStreamCommands.XAddOptions}. - * @since 6.5 - */ - public void setAddOptionsFunction(Function, RedisStreamCommands.XAddOptions> addOptionsFunction) { - this.addOptionsFunction = addOptionsFunction; - } - - @Override - public String getComponentType() { - return "redis:stream-outbound-channel-adapter"; - } - - @Override - @SuppressWarnings("unchecked") - protected void onInit() { - super.onInit(); - - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - - ReactiveRedisTemplate template = - new ReactiveRedisTemplate<>(this.connectionFactory, this.serializationContext); - this.reactiveStreamOperations = - this.hashMapper == null - ? template.opsForStream() - : template.opsForStream( - (HashMapper) this.hashMapper); - } - - @Override - protected Mono handleMessageInternal(Message message) { - return Mono - .fromSupplier(() -> { - String streamKey = this.streamKeyExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(streamKey, "'streamKey' must not be null"); - return streamKey; - }) - .flatMap((streamKey) -> { - Object value = message; - if (this.extractPayload) { - value = message.getPayload(); - } - - Record record = - StreamRecords.objectBacked(value) - .withStreamKey(streamKey); - - if (this.addOptionsFunction == null) { - return this.reactiveStreamOperations.add(record); - } - else { - return this.reactiveStreamOperations.add(record, this.addOptionsFunction.apply(message)); - } - }) - .then(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisOutboundGateway.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisOutboundGateway.java deleted file mode 100644 index 371e6c4decd..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisOutboundGateway.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisCallback; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericToStringSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * The Gateway component implementation to perform Redis commands with provided arguments and to return command result. - * - * @author Artem Bilan - * @author Gary Russell - * @since 4.0 - */ -public class RedisOutboundGateway extends AbstractReplyProducingMessageHandler { - - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - - private static final byte[][] EMPTY_ARGS = new byte[0][]; - - private final RedisTemplate redisTemplate; - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - private RedisSerializer argumentsSerializer = new GenericToStringSerializer<>(Object.class); - - @SuppressWarnings("NullAway") // Cannot see nullability on the generics - private Expression commandExpression = - new FunctionExpression>(message -> message.getHeaders().get(RedisHeaders.COMMAND)); - - private @Nullable ArgumentsStrategy argumentsStrategy = new PayloadArgumentsStrategy(); - - public RedisOutboundGateway(RedisTemplate redisTemplate) { - Assert.notNull(redisTemplate, "'redisTemplate' must not be null"); - this.redisTemplate = redisTemplate; - } - - public RedisOutboundGateway(RedisConnectionFactory connectionFactory) { - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - this.redisTemplate = new RedisTemplate<>(); - this.redisTemplate.setConnectionFactory(connectionFactory); - this.redisTemplate.afterPropertiesSet(); - } - - @SuppressWarnings("unchecked") - public void setArgumentsSerializer(RedisSerializer serializer) { - Assert.notNull(serializer, "'serializer' must not be null"); - this.argumentsSerializer = (RedisSerializer) serializer; - } - - /** - * @param commandExpression the String in SpEL syntax. - * @since 4.3 - */ - public void setCommandExpression(Expression commandExpression) { - this.commandExpression = commandExpression; - } - - /** - * @param commandExpression the String in SpEL syntax. - * @since 4.3 - */ - public void setCommandExpressionString(String commandExpression) { - Assert.hasText(commandExpression, "'commandExpression' must not be empty"); - this.commandExpression = EXPRESSION_PARSER.parseExpression(commandExpression); - } - - public void setArgumentsStrategy(@Nullable ArgumentsStrategy argumentsStrategy) { - this.argumentsStrategy = argumentsStrategy; - } - - @Override - public String getComponentType() { - return "redis:outbound-gateway"; - } - - @Override - protected void doInit() { - super.doInit(); - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - } - - @Override - protected @Nullable Object handleRequestMessage(Message requestMessage) { - final String command = this.commandExpression.getValue(this.evaluationContext, requestMessage, String.class); - Assert.notNull(command, "The 'command' must not evaluate to 'null'."); - byte[][] args = null; - if (this.argumentsStrategy != null) { - Object[] arguments = this.argumentsStrategy.resolve(command, requestMessage); - if (!ObjectUtils.isEmpty(arguments)) { - args = new byte[arguments.length][]; - - for (int i = 0; i < arguments.length; i++) { - Object argument = arguments[i]; - args[i] = argument instanceof byte[] bytes ? bytes : this.argumentsSerializer.serialize(argument); - } - } - } - - final byte[][] actualArgs = args != null ? args : EMPTY_ARGS; - - return this.redisTemplate.execute( - (RedisCallback<@Nullable Object>) connection -> connection.execute(command, actualArgs)); - } - - private static class PayloadArgumentsStrategy implements ArgumentsStrategy { - - PayloadArgumentsStrategy() { - } - - @Override - public Object[] resolve(String command, Message message) { - Object payload = message.getPayload(); - if (payload instanceof Object[]) { - return (Object[]) payload; - } - else { - return new Object[] {payload}; - } - } - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java deleted file mode 100644 index 2664b35da1e..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.integration.support.converter.SimpleMessageConverter; -import org.springframework.messaging.Message; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.util.Assert; - -/** - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.1 - */ -public class RedisPublishingMessageHandler extends AbstractMessageHandler { - - private final RedisTemplate template; - - @SuppressWarnings("NullAway.Init") - private volatile EvaluationContext evaluationContext; - - private volatile MessageConverter messageConverter = new SimpleMessageConverter(); - - private volatile RedisSerializer serializer = new StringRedisSerializer(); - - @SuppressWarnings("NullAway.Init") - private volatile Expression topicExpression; - - public RedisPublishingMessageHandler(RedisConnectionFactory connectionFactory) { - Assert.notNull(connectionFactory, "connectionFactory must not be null"); - this.template = new RedisTemplate(); - this.template.setConnectionFactory(connectionFactory); - this.template.setEnableDefaultSerializer(false); - this.template.afterPropertiesSet(); - } - - public void setSerializer(RedisSerializer serializer) { - Assert.notNull(serializer, "'serializer' must not be null"); - this.serializer = serializer; - } - - public void setMessageConverter(MessageConverter messageConverter) { - Assert.notNull(messageConverter, "messageConverter must not be null"); - this.messageConverter = messageConverter; - } - - public void setTopic(String topic) { - Assert.hasText(topic, "'topic' must not be an empty string."); - this.setTopicExpression(new LiteralExpression(topic)); - } - - public void setTopicExpression(Expression topicExpression) { - Assert.notNull(topicExpression, "'topicExpression' must not be null."); - this.topicExpression = topicExpression; - } - - public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { - this.evaluationContext = evaluationContext; - } - - @Override - public String getComponentType() { - return "redis:outbound-channel-adapter"; - } - - @Override - protected void onInit() { - Assert.notNull(this.topicExpression, "'topicExpression' must not be null."); - if (this.messageConverter instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.messageConverter).setBeanFactory(getBeanFactory()); - } - if (this.evaluationContext == null) { - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - } - } - - @Override - @SuppressWarnings("unchecked") - protected void handleMessageInternal(Message message) { - String topic = this.topicExpression.getValue(this.evaluationContext, message, String.class); - Object value = this.messageConverter.fromMessage(message, Object.class); - Assert.notNull(topic, "'topic' must not be null"); - Assert.notNull(value, "'value' must not be null"); - - if (value instanceof byte[]) { - this.template.convertAndSend(topic, value); // NOSONAR - } - else { - this.template.convertAndSend(topic, ((RedisSerializer) this.serializer).serialize(value)); // NOSONAR - } - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java deleted file mode 100644 index 0f76a05b0f6..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * @author Mark Fisher - * @author Gunnar Hillert - * @author Artem Bilan - * @author Rainer Frey - * @since 3.0 - */ -public class RedisQueueOutboundChannelAdapter extends AbstractMessageHandler { - - private final RedisSerializer stringSerializer = new StringRedisSerializer(); - - private final RedisTemplate template; - - private final Expression queueNameExpression; - - @SuppressWarnings("NullAway.Init") - private volatile EvaluationContext evaluationContext; - - private volatile boolean extractPayload = true; - - private volatile RedisSerializer serializer = new JdkSerializationRedisSerializer(); - - private volatile boolean serializerExplicitlySet; - - private volatile boolean leftPush = true; - - public RedisQueueOutboundChannelAdapter(String queueName, RedisConnectionFactory connectionFactory) { - this(new LiteralExpression(queueName), connectionFactory); - } - - public RedisQueueOutboundChannelAdapter(Expression queueNameExpression, RedisConnectionFactory connectionFactory) { - Assert.notNull(queueNameExpression, "'queueNameExpression' is required"); - Assert.hasText(queueNameExpression.getExpressionString(), "'queueNameExpression.getExpressionString()' is required"); - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - this.queueNameExpression = queueNameExpression; - this.template = new RedisTemplate(); - this.template.setConnectionFactory(connectionFactory); - this.template.setEnableDefaultSerializer(false); - this.template.setKeySerializer(new StringRedisSerializer()); - this.template.afterPropertiesSet(); - } - - public void setExtractPayload(boolean extractPayload) { - this.extractPayload = extractPayload; - } - - public void setSerializer(RedisSerializer serializer) { - Assert.notNull(serializer, "'serializer' must not be null"); - this.serializer = serializer; - this.serializerExplicitlySet = true; - } - - /** - * Specify if {@code PUSH} operation to Redis List should be {@code LPUSH} or {@code RPUSH}. - * @param leftPush the {@code LPUSH} flag. Defaults to {@code true}. - * @since 4.3 - */ - public void setLeftPush(boolean leftPush) { - this.leftPush = leftPush; - } - - public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { - this.evaluationContext = evaluationContext; - } - - @Override - public String getComponentType() { - return "redis:queue-outbound-channel-adapter"; - } - - @Override - protected void onInit() { - super.onInit(); - if (this.evaluationContext == null) { - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - } - } - - @Override - @SuppressWarnings("unchecked") - protected void handleMessageInternal(Message message) { - Object value = message; - - if (this.extractPayload) { - value = message.getPayload(); - } - - if (!(value instanceof byte[])) { - if (value instanceof String && !this.serializerExplicitlySet) { - value = this.stringSerializer.serialize((String) value); - } - else { - value = ((RedisSerializer) this.serializer).serialize(value); - } - } - - String queueName = this.queueNameExpression.getValue(this.evaluationContext, message, String.class); - Assert.notNull(queueName, "'queueName' must not be null"); - Assert.notNull(value, "'value' must not be null"); - if (this.leftPush) { - this.template.boundListOps(queueName).leftPush(value); // NOSONAR - } - else { - this.template.boundListOps(queueName).rightPush(value); // NOSONAR - } - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundGateway.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundGateway.java deleted file mode 100644 index 1de1e29b266..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundGateway.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.concurrent.TimeUnit; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundListOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.util.AlternativeJdkIdGenerator; -import org.springframework.util.Assert; -import org.springframework.util.IdGenerator; - -/** - * @author David Liu - * @author Artem Bilan - * @author Gary Russell - * - * @since 4.1 - */ -public class RedisQueueOutboundGateway extends AbstractReplyProducingMessageHandler { - - private static final String QUEUE_NAME_SUFFIX = ".reply"; - - private static final int TIMEOUT = 1000; - - private static final IdGenerator DEFAULT_ID_GENERATOR = new AlternativeJdkIdGenerator(); - - private static final RedisSerializer STRING_SERIALIZER = new StringRedisSerializer(); - - private final RedisTemplate template = new RedisTemplate<>(); - - private final BoundListOperations boundListOps; - - private boolean extractPayload = true; - - @SuppressWarnings("NullAway.Init") - private RedisSerializer serializer; - - private boolean serializerExplicitlySet; - - private int receiveTimeout = TIMEOUT; - - public RedisQueueOutboundGateway(String queueName, RedisConnectionFactory connectionFactory) { - Assert.hasText(queueName, "'queueName' is required"); - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - this.template.setConnectionFactory(connectionFactory); - this.template.setEnableDefaultSerializer(false); - this.template.setKeySerializer(new StringRedisSerializer()); - this.template.afterPropertiesSet(); - this.boundListOps = this.template.boundListOps(queueName); - } - - @Override - public void setBeanClassLoader(ClassLoader beanClassLoader) { - super.setBeanClassLoader(beanClassLoader); - if (!this.serializerExplicitlySet) { - this.serializer = new JdkSerializationRedisSerializer(beanClassLoader); - } - } - - public void setReceiveTimeout(int timeout) { - this.receiveTimeout = timeout; - } - - public void setExtractPayload(boolean extractPayload) { - this.extractPayload = extractPayload; - } - - public void setSerializer(RedisSerializer serializer) { - Assert.notNull(serializer, "'serializer' must not be null"); - this.serializer = serializer; - this.serializerExplicitlySet = true; - } - - @Override - public String getComponentType() { - return "redis:queue-outbound-gateway"; - } - - @Override - @SuppressWarnings("unchecked") - @Nullable - protected Object handleRequestMessage(Message message) { - Object value = message; - - if (this.extractPayload) { - value = message.getPayload(); - } - Object beforeSerialization = value; - if (!(value instanceof byte[])) { - if (value instanceof String && !this.serializerExplicitlySet) { - value = STRING_SERIALIZER.serialize((String) value); - } - else { - Assert.state(this.serializer != null, "'serializer' must not be null"); - value = ((RedisSerializer) this.serializer).serialize(value); - } - } - if (value == null) { - this.logger.debug(() -> "Serializer produced null for " + beforeSerialization); - return null; - } - String uuid = DEFAULT_ID_GENERATOR.generateId().toString(); - - byte[] uuidByte = uuid.getBytes(); - this.boundListOps.leftPush(uuidByte); - this.template.boundListOps(uuid).leftPush(value); - - BoundListOperations boundListOperations = this.template.boundListOps(uuid + QUEUE_NAME_SUFFIX); - byte[] reply = (byte[]) boundListOperations.rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS); - if (reply != null && reply.length > 0) { - return createReply(reply); - } - return null; - } - - @Nullable - private Object createReply(byte[] reply) { - Assert.state(this.serializer != null, "'serializer' must not be null"); - Object replyMessage = this.serializer.deserialize(reply); - if (replyMessage == null) { - return null; - } - if (this.extractPayload) { - return getMessageBuilderFactory() - .withPayload(replyMessage); - } - else { - return replyMessage; - } - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisStoreWritingMessageHandler.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisStoreWritingMessageHandler.java deleted file mode 100644 index 028ed818ca4..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisStoreWritingMessageHandler.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; - -import org.springframework.core.log.LogMessage; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundSetOperations; -import org.springframework.data.redis.core.BoundZSetOperations; -import org.springframework.data.redis.core.RedisConnectionUtils; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.data.redis.support.collections.RedisCollectionFactoryBean; -import org.springframework.data.redis.support.collections.RedisCollectionFactoryBean.CollectionType; -import org.springframework.data.redis.support.collections.RedisList; -import org.springframework.data.redis.support.collections.RedisMap; -import org.springframework.data.redis.support.collections.RedisProperties; -import org.springframework.data.redis.support.collections.RedisSet; -import org.springframework.data.redis.support.collections.RedisStore; -import org.springframework.data.redis.support.collections.RedisZSet; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; -import org.springframework.util.NumberUtils; - -/** - * Implementation of {@link org.springframework.messaging.MessageHandler} which writes - * Message data into a Redis store identified by a key {@link String}. - * - * It supports the collection types identified by {@link CollectionType}. - * - * It supports batch updates or single item entry. - * - * "Batch updates" means that the payload of the Message may be a Map or Collection. With - * such a payload, individual items from it are added to the corresponding Redis store. - * See {@link #handleMessageInternal(Message)} for more details. - * - * You can instead choose to persist such a payload as a single item if the - * {@link #extractPayloadElements} property is set to false (default is true). - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Mark Fisher - * @author Artem Bilan - * @author Trung Pham - * - * @since 2.2 - */ -public class RedisStoreWritingMessageHandler extends AbstractMessageHandler { - - @SuppressWarnings("NullAway") // Lambda can return null from getHeaders().get() - private Expression zsetIncrementScoreExpression = - new FunctionExpression>(m -> - m.getHeaders().get(RedisHeaders.ZSET_INCREMENT_SCORE)); - - @SuppressWarnings("NullAway") // Lambda can return null from getHeaders().get() - private Expression keyExpression = - new FunctionExpression>(m -> - m.getHeaders().get(RedisHeaders.KEY)); - - @SuppressWarnings("NullAway") // Lambda can return null from getHeaders().get() - private Expression mapKeyExpression = - new FunctionExpression>(m -> - m.getHeaders().get(RedisHeaders.MAP_KEY)); - - private boolean mapKeyExpressionExplicitlySet; - - @SuppressWarnings("NullAway.Init") - private StandardEvaluationContext evaluationContext; - - private RedisTemplate redisTemplate = new StringRedisTemplate(); - - private CollectionType collectionType = CollectionType.LIST; - - private boolean extractPayloadElements = true; - - private final RedisConnectionFactory connectionFactory; - - private volatile boolean initialized; - - /** - * Constructs an instance using the provided {@link RedisTemplate}. - * The RedisTemplate must be fully initialized. - * @param redisTemplate The Redis template. - */ - public RedisStoreWritingMessageHandler(RedisTemplate redisTemplate) { - Assert.notNull(redisTemplate, "'redisTemplate' must not be null"); - RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory(); - Assert.notNull(connectionFactory, "'redisTemplate.connectionFactory' must not be null"); - this.redisTemplate = redisTemplate; - this.connectionFactory = connectionFactory; - } - - /** - * Constructs an instance using the provided {@link RedisConnectionFactory}. - * It will use either a {@link StringRedisTemplate} if {@link #extractPayloadElements} is - * true (default) or a {@link RedisTemplate} with {@link StringRedisSerializer}s for - * keys and hash keys and - * {@link org.springframework.data.redis.serializer.JdkSerializationRedisSerializer}s - * for values and - * hash values, when it is false. - * @param connectionFactory The connection factory. - * @see #setExtractPayloadElements(boolean) - */ - public RedisStoreWritingMessageHandler(RedisConnectionFactory connectionFactory) { - Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); - this.connectionFactory = connectionFactory; - } - - /** - * Specifies the key for the Redis store. If an expression is needed, then call - * {@link #setKeyExpression(Expression)} instead of this method (they are mutually exclusive). - * If neither setter is called, the default expression will be 'headers.{@link RedisHeaders#KEY}'. - * @param key The key. - * @see #setKeyExpression - */ - public void setKey(String key) { - Assert.hasText(key, "key must not be empty"); - this.setKeyExpression(new LiteralExpression(key)); - } - - /** - * Specifies a SpEL Expression to be used to determine the key for the Redis store. - * If an expression is not needed, then a literal value may be passed to the - * {@link #setKey(String)} method instead of this one (they are mutually exclusive). - * If neither setter is called, the default expression will be 'headers.{@link RedisHeaders#KEY}'. - * @param keyExpression The key expression. - * @since 5.0 - * @see #setKey(String) - */ - public void setKeyExpressionString(String keyExpression) { - Assert.hasText(keyExpression, "'keyExpression' must not be empty"); - setKeyExpression(EXPRESSION_PARSER.parseExpression(keyExpression)); - } - - /** - * Specifies a SpEL Expression to be used to determine the key for the Redis store. - * If an expression is not needed, then a literal value may be passed to the - * {@link #setKey(String)} method instead of this one (they are mutually exclusive). - * If neither setter is called, the default expression will be 'headers.{@link RedisHeaders#KEY}'. - * @param keyExpression The key expression. - * @see #setKey(String) - */ - public void setKeyExpression(Expression keyExpression) { - Assert.notNull(keyExpression, "keyExpression must not be null"); - this.keyExpression = keyExpression; - } - - /** - * Sets the collection type for this handler as per {@link CollectionType}. - * @param collectionType The collection type. - */ - public void setCollectionType(CollectionType collectionType) { - this.collectionType = collectionType; - } - - /** - * Sets the flag signifying that if the payload is a "multivalue" (i.e., Collection or Map), - * it should be saved using addAll/putAll semantics. Default is 'true'. - * If set to 'false' the payload will be saved as a single entry regardless of its type. - * If the payload is not an instance of "multivalue" (i.e., Collection or Map), - * the value of this attribute is meaningless as the payload will always be - * stored as a single entry. - * @param extractPayloadElements true if payload elements should be extracted. - */ - public void setExtractPayloadElements(boolean extractPayloadElements) { - this.extractPayloadElements = extractPayloadElements; - } - - /** - * Sets the expression used as the key for Map and Properties entries. - * Default is 'headers.{@link RedisHeaders#MAP_KEY}' - * @param mapKeyExpression The map key expression. - * @since 5.0 - */ - public void setMapKeyExpressionString(String mapKeyExpression) { - Assert.hasText(mapKeyExpression, "'mapKeyExpression' must not be empty"); - setMapKeyExpression(EXPRESSION_PARSER.parseExpression(mapKeyExpression)); - } - - /** - * Sets the expression used as the key for Map and Properties entries. - * Default is 'headers.{@link RedisHeaders#MAP_KEY}' - * @param mapKeyExpression The map key expression. - */ - public void setMapKeyExpression(Expression mapKeyExpression) { - Assert.notNull(mapKeyExpression, "'mapKeyExpression' must not be null"); - this.mapKeyExpression = mapKeyExpression; - this.mapKeyExpressionExplicitlySet = true; - } - - /** - * Set the expression used as the INCR flag for the ZADD command in case of ZSet collection. - * Default is 'headers.{@link RedisHeaders#ZSET_INCREMENT_SCORE}' - * @param zsetIncrementScoreExpression The ZADD INCR flag expression. - * @since 5.0 - */ - public void setZsetIncrementExpressionString(String zsetIncrementScoreExpression) { - Assert.hasText(zsetIncrementScoreExpression, "'zsetIncrementScoreExpression' must not be empty"); - setZsetIncrementExpression(EXPRESSION_PARSER.parseExpression(zsetIncrementScoreExpression)); - } - - /** - * Set the expression used as the INCR flag for the ZADD command in case of ZSet collection. - * Default is 'headers.{@link RedisHeaders#ZSET_INCREMENT_SCORE}' - * @param zsetIncrementScoreExpression The ZADD INCR flag expression. - * @since 5.0 - */ - public void setZsetIncrementExpression(Expression zsetIncrementScoreExpression) { - Assert.notNull(zsetIncrementScoreExpression, "'zsetIncrementScoreExpression' must not be null"); - this.zsetIncrementScoreExpression = zsetIncrementScoreExpression; - } - - @Override - public String getComponentType() { - return "redis:store-outbound-channel-adapter"; - } - - @Override - protected void onInit() { - this.evaluationContext = - ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); - Assert.state(!this.mapKeyExpressionExplicitlySet || - (this.collectionType == CollectionType.MAP || this.collectionType == CollectionType.PROPERTIES), - "'mapKeyExpression' can only be set for CollectionType.MAP or CollectionType.PROPERTIES"); - if (this.redisTemplate instanceof StringRedisTemplate) { - if (!this.extractPayloadElements) { - RedisTemplate template = new RedisTemplate<>(); - StringRedisSerializer serializer = new StringRedisSerializer(); - template.setKeySerializer(serializer); - template.setHashKeySerializer(serializer); - this.redisTemplate = template; - } - this.redisTemplate.setConnectionFactory(this.connectionFactory); - this.redisTemplate.afterPropertiesSet(); - } - this.initialized = true; - } - - /** - * Will extract the payload from the Message and store it in the collection identified by the - * key (which may be determined by an expression). The type of collection is specified by the - * {@link #collectionType} property. The default CollectionType is LIST. - *

- * The rules for storing the payload are: - *

- * LIST/SET - * If the payload is of type Collection and {@link #extractPayloadElements} is 'true' (default), - * the payload will be added using the addAll() method. If {@link #extractPayloadElements} - * is set to 'false', then regardless of the payload type, the payload will be added using add(). - *

- * ZSET - * In addition to the rules described for LIST/SET, ZSET allows 'score' information - * to be provided. The score can be provided using the {@link RedisHeaders#ZSET_SCORE} message header - * when the payload is not a Map, or by sending a Map as the payload where each Map 'key' is a - * value to be saved and each corresponding Map 'value' is the score assigned to it. - * If {@link #extractPayloadElements} is set to 'false' the map will be stored as a single entry. - * If the 'score' can not be determined, the default value (1) will be used. - *

- * MAP/PROPERTIES - * You can also add items to a Map or Properties based store. - * If the payload itself is of type Map or Properties, it can be stored either as a batch or single - * item following the same rules as described above for other collection types. - * If the payload itself needs to be stored as a value of the map/property then the map key - * must be specified via the mapKeyExpression (default {@link RedisHeaders#MAP_KEY} Message header). - */ - @SuppressWarnings("unchecked") - @Override - protected void handleMessageInternal(Message message) { - String key = this.keyExpression.getValue(this.evaluationContext, message, String.class); - Assert.hasText(key, () -> "Failed to determine a key for the Redis store based on the message: " + message); - - RedisStore store = createStoreView(key); - - Assert.state(this.initialized, - "handler not initialized - afterPropertiesSet() must be called before the first use"); - try { - if (this.collectionType == CollectionType.ZSET) { - writeToZset((RedisZSet) store, message); - } - else if (this.collectionType == CollectionType.SET) { - writeToSet((RedisSet) store, message); - } - else if (this.collectionType == CollectionType.LIST) { - writeToList((RedisList) store, message); - } - else if (this.collectionType == CollectionType.MAP) { - writeToMap((RedisMap) store, message); - } - else if (this.collectionType == CollectionType.PROPERTIES) { - writeToProperties((RedisProperties) store, message); - } - } - catch (Exception ex) { - throw IntegrationUtils.wrapInHandlingExceptionIfNecessary(message, - () -> "Failed to store Message data into Redis collection in the [" + this + ']', ex); - } - } - - @SuppressWarnings("unchecked") - private void writeToZset(RedisZSet zset, final Message message) { - final Object payload = message.getPayload(); - final BoundZSetOperations ops = - (BoundZSetOperations) this.redisTemplate.boundZSetOps(zset.getKey()); - boolean zsetIncrementHeader = extractZsetIncrementHeader(message); - if (this.extractPayloadElements) { - if ((payload instanceof Map && this.verifyAllMapValuesOfTypeNumber((Map) payload))) { - Map payloadAsMap = (Map) payload; - processInPipeline(() -> { - for (Entry entry : payloadAsMap.entrySet()) { - Number d = entry.getValue(); - incrementOrOverwrite(ops, entry.getKey(), d == null ? - determineScore(message) : - NumberUtils.convertNumberToTargetClass(d, Double.class), - zsetIncrementHeader); - } - }); - } - else if (payload instanceof Collection) { - processInPipeline(() -> { - for (Object object : ((Collection) payload)) { - incrementOrOverwrite(ops, object, determineScore(message), zsetIncrementHeader); - } - }); - } - else { - incrementOrOverwrite(ops, payload, this.determineScore(message), zsetIncrementHeader); - } - } - else { - incrementOrOverwrite(ops, payload, this.determineScore(message), zsetIncrementHeader); - } - } - - private boolean extractZsetIncrementHeader(Message message) { - Boolean value = this.zsetIncrementScoreExpression.getValue(this.evaluationContext, message, Boolean.class); - return value != null ? value : false; - } - - private void writeToList(RedisList list, Message message) { - Object payload = message.getPayload(); - if (this.extractPayloadElements) { - if (payload instanceof Collection) { - list.addAll((Collection) payload); - } - else { - list.add(payload); - } - } - else { - list.add(payload); - } - } - - @SuppressWarnings("unchecked") - private void writeToSet(final RedisSet set, Message message) { - final Object payload = message.getPayload(); - if (this.extractPayloadElements && payload instanceof Collection) { - BoundSetOperations ops = - (BoundSetOperations) this.redisTemplate.boundSetOps(set.getKey()); - - processInPipeline(() -> { - for (Object object : ((Collection) payload)) { - ops.add(object); - } - }); - } - else { - set.add(payload); - } - } - - private void writeToMap(final RedisMap map, Message message) { - final Object payload = message.getPayload(); - if (this.extractPayloadElements && payload instanceof Map) { - processInPipeline(() -> map.putAll((Map) payload)); - } - else { - Object key = this.determineMapKey(message, false); - map.put(key, payload); - } - } - - private void writeToProperties(final RedisProperties properties, Message message) { - final Object payload = message.getPayload(); - if (this.extractPayloadElements && payload instanceof Properties) { - processInPipeline(() -> properties.putAll((Properties) payload)); - } - else { - Assert.isInstanceOf(String.class, payload, "For property, payload must be a String."); - Object key = this.determineMapKey(message, true); - properties.put(key, payload); - } - } - - private void processInPipeline(PipelineCallback callback) { - RedisConnectionFactory connectionFactoryForPipeline = this.redisTemplate.getConnectionFactory(); - Assert.state(connectionFactoryForPipeline != null, "RedisTemplate returned no connection factory"); - RedisConnection connection = - RedisConnectionUtils.bindConnection(connectionFactoryForPipeline); - try { - connection.openPipeline(); - callback.process(); - } - finally { - connection.closePipeline(); - RedisConnectionUtils.unbindConnection(connectionFactoryForPipeline); - } - } - - private Object determineMapKey(Message message, boolean property) { - Object mapKey = this.mapKeyExpression.getValue(this.evaluationContext, message); - Assert.notNull(mapKey, () -> "Cannot determine a map key for the entry based on the message: " + message); - if (property) { - Assert.isInstanceOf(String.class, mapKey, "For property, key must be a String"); - } - return mapKey; - } - - private void incrementOrOverwrite(BoundZSetOperations ops, Object object, Double score, - boolean zsetIncrementScore) { - if (score != null) { - doIncrementOrOverwrite(ops, object, score, zsetIncrementScore); - } - else { - this.logger.debug("Zset Score could not be determined. Using default score of 1"); - doIncrementOrOverwrite(ops, object, 1d, zsetIncrementScore); - } - } - - private void doIncrementOrOverwrite(BoundZSetOperations ops, Object object, Double score, - boolean increment) { - if (increment) { - ops.incrementScore(object, score); - } - else { - ops.add(object, score); - } - } - - private boolean verifyAllMapValuesOfTypeNumber(Map map) { - for (Object value : map.values()) { - if (!(value instanceof Number)) { - this.logger.warn(LogMessage.format("failed to extract payload elements" - + "because '%s' is not of type Number", value)); - return false; - } - } - return true; - } - - private RedisStore createStoreView(String key) { - RedisCollectionFactoryBean fb = new RedisCollectionFactoryBean(); - fb.setKey(key); - fb.setTemplate(this.redisTemplate); - fb.setType(this.collectionType); - fb.afterPropertiesSet(); - return fb.getObject(); - } - - private double determineScore(Message message) { - Object scoreHeader = message.getHeaders().get(RedisHeaders.ZSET_SCORE); - if (scoreHeader == null) { - return 1d; - } - else { - Assert.isInstanceOf(Number.class, scoreHeader, - () -> "Header " + RedisHeaders.ZSET_SCORE + " must be a Number"); - Number score = (Number) scoreHeader; - return Double.valueOf(score.toString()); - } - } - - private interface PipelineCallback { - - void process(); - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/package-info.java deleted file mode 100644 index cf6c3713a76..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting outbound endpoints. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.outbound; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisChannelMessageStore.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisChannelMessageStore.java deleted file mode 100644 index 169b78ca63f..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisChannelMessageStore.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.List; -import java.util.Set; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.store.ChannelMessageStore; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupFactory; -import org.springframework.integration.store.SimpleMessageGroupFactory; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Specialized Redis {@link ChannelMessageStore} that uses a list to back a QueueChannel. - *

- * Requires {@link #setBeanName(String)} which is used as part of the key. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.0 - * - */ -public class RedisChannelMessageStore - implements ChannelMessageStore, BeanNameAware, InitializingBean, BeanClassLoaderAware { - - private final RedisTemplate> redisTemplate; - - @SuppressWarnings("NullAway.Init") - private String beanName; - - private MessageGroupFactory messageGroupFactory = new SimpleMessageGroupFactory(); - - private boolean valueSerializerExplicitlySet; - - /** - * Construct a message store that uses Java Serialization for messages. - * - * @param connectionFactory The redis connection factory. - */ - public RedisChannelMessageStore(RedisConnectionFactory connectionFactory) { - this.redisTemplate = new RedisTemplate<>(); - this.redisTemplate.setConnectionFactory(connectionFactory); - this.redisTemplate.setKeySerializer(new StringRedisSerializer()); - this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - this.redisTemplate.afterPropertiesSet(); - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - if (!this.valueSerializerExplicitlySet) { - this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer(classLoader)); - } - } - - /** - * Use a different serializer (default {@link JdkSerializationRedisSerializer} for - * the {@link Message}. - * - * @param valueSerializer The value serializer. - */ - public void setValueSerializer(RedisSerializer valueSerializer) { - Assert.notNull(valueSerializer, "'valueSerializer' must not be null"); - this.redisTemplate.setValueSerializer(valueSerializer); - this.valueSerializerExplicitlySet = true; - } - - /** - * Specify the {@link MessageGroupFactory} to create {@link MessageGroup} object where - * it is necessary. - * Defaults to {@link SimpleMessageGroupFactory}. - * @param messageGroupFactory the {@link MessageGroupFactory} to use. - * @since 4.3 - */ - public void setMessageGroupFactory(MessageGroupFactory messageGroupFactory) { - Assert.notNull(messageGroupFactory, "'messageGroupFactory' must not be null"); - this.messageGroupFactory = messageGroupFactory; - } - - protected MessageGroupFactory getMessageGroupFactory() { - return this.messageGroupFactory; - } - - @Override - public void setBeanName(String name) { - Assert.notNull(name, "'beanName' must not be null"); - this.beanName = name; - } - - protected String getBeanName() { - return this.beanName; - } - - protected RedisTemplate> getRedisTemplate() { - return this.redisTemplate; - } - - @Override - public void afterPropertiesSet() { - Assert.notNull(this.beanName, "'beanName' must not be null"); - } - - @Override - @ManagedAttribute - public int messageGroupSize(Object groupId) { - Long size = this.redisTemplate.boundListOps(groupId).size(); - return size == null ? 0 : (int) size.longValue(); - } - - @Override - public MessageGroup getMessageGroup(Object groupId) { - List> messages = this.redisTemplate.boundListOps(groupId).range(0, -1); - return getMessageGroupFactory().create(messages, groupId); - } - - @Override - public MessageGroup addMessageToGroup(Object groupId, Message message) { - this.redisTemplate.boundListOps(groupId).leftPush(message); - return getMessageGroup(groupId); - } - - @Override - public void removeMessageGroup(Object groupId) { - this.redisTemplate.boundListOps(groupId).trim(1, 0); - } - - @Override - public @Nullable Message pollMessageFromGroup(Object groupId) { - return this.redisTemplate.boundListOps(groupId).rightPop(); - } - - @ManagedAttribute - public int getMessageCountForAllMessageGroups() { - Set keys = this.redisTemplate.keys(this.beanName + ":*"); - if (keys == null) { - return 0; - } - int count = 0; - for (Object key : keys) { - count += this.messageGroupSize(key); - } - return count; - } - - @ManagedAttribute - public int getMessageGroupCount() { - Set keys = this.redisTemplate.keys(this.beanName + ":*"); - return keys == null ? 0 : keys.size(); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisChannelPriorityMessageStore.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisChannelPriorityMessageStore.java deleted file mode 100644 index 49517b27a54..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisChannelPriorityMessageStore.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import org.jspecify.annotations.Nullable; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.PriorityCapableChannelMessageStore; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.messaging.Message; -import org.springframework.util.Assert; - -/** - * Specialized Redis {@link PriorityCapableChannelMessageStore} that uses lists to back a QueueChannel. - * Messages are removed in priority order ({@link IntegrationMessageHeaderAccessor#PRIORITY}). - * Priorities 0-9 are supported (9 the highest); invalid priority values are treated with the same priority (none) - * as messages with no priority header (retrieved after any messages that have a priority). - *

- * Requires that groupId is a String. - * - * @author Gary Russell - * @author Artem Bilan - * @since 4.0 - * - */ -public class RedisChannelPriorityMessageStore extends RedisChannelMessageStore - implements PriorityCapableChannelMessageStore { - - private final Comparator keysComparator = (s1, s2) -> s2.compareTo(s1); - - public RedisChannelPriorityMessageStore(RedisConnectionFactory connectionFactory) { - super(connectionFactory); - } - - @Override - public boolean isPriorityEnabled() { - return true; - } - - @Override - @ManagedAttribute - public int messageGroupSize(Object groupId) { - Assert.isInstanceOf(String.class, groupId); - List list = sortedKeys((String) groupId); - int count = 0; - for (String key : list) { - Long size = getRedisTemplate().boundListOps(key).size(); - if (size != null) { - count += size; - } - } - return count; - } - - @Override - public MessageGroup getMessageGroup(Object groupId) { - Assert.isInstanceOf(String.class, groupId); - List> allMessages = new LinkedList>(); - List list = sortedKeys((String) groupId); - for (String key : list) { - List> messages = this.getRedisTemplate().boundListOps(key).range(0, -1); - if (messages != null) { - allMessages.addAll(messages); - } - } - return getMessageGroupFactory().create(allMessages, groupId); - } - - @Override - public MessageGroup addMessageToGroup(Object groupId, Message message) { - Assert.isInstanceOf(String.class, groupId); - String key = (String) groupId; - Integer priority = new IntegrationMessageHeaderAccessor(message).getPriority(); - if (priority != null && priority < 10 && priority >= 0) { // NOSONAR magic number - key = key + ":" + priority; - } - return super.addMessageToGroup(key, message); - } - - @Override - public @Nullable Message pollMessageFromGroup(Object groupId) { - Assert.isInstanceOf(String.class, groupId); - List list = sortedKeys((String) groupId); - Message message; - for (String key : list) { - message = super.pollMessageFromGroup(key); - if (message != null) { - return message; - } - } - return null; - } - - private List sortedKeys(String groupId) { - Set keys = this.getRedisTemplate().keys(groupId == null ? (this.getBeanName() + ":*") : (groupId + "*")); - List list = new LinkedList<>(); - if (keys != null) { - for (Object key : keys) { - Assert.isInstanceOf(String.class, key); - list.add((String) key); - } - Collections.sort(list, this.keysComparator); - } - return list; - } - - @Override - @ManagedAttribute - public int getMessageGroupCount() { - Set narrowedKeys = narrowedKeys(); - return narrowedKeys.size(); - } - - private Set narrowedKeys() { - Set keys = this.getRedisTemplate().keys(this.getBeanName() + ":*"); - Set narrowedKeys = new HashSet<>(); - if (keys != null) { - for (Object key : keys) { - Assert.isInstanceOf(String.class, key); - String keyString = (String) key; - int lastIndexOfColon = keyString.lastIndexOf(':'); - if (keyString.indexOf(':') != lastIndexOfColon) { - narrowedKeys.add(keyString.substring(0, lastIndexOfColon)); - } - else { - narrowedKeys.add(key); - } - } - } - return narrowedKeys; - } - - @Override - public void removeMessageGroup(Object groupId) { - Assert.isInstanceOf(String.class, groupId); - List list = sortedKeys((String) groupId); - for (String key : list) { - super.removeMessageGroup(key); - } - } - - @Override - @ManagedAttribute - public int getMessageCountForAllMessageGroups() { - Set narrowedKeys = narrowedKeys(); - int count = 0; - for (Object key : narrowedKeys) { - count += this.messageGroupSize(key); - } - return count; - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisMessageStore.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisMessageStore.java deleted file mode 100644 index 512f254ebe5..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/RedisMessageStore.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.Collection; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundValueOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.SerializationException; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.store.AbstractKeyValueMessageStore; -import org.springframework.util.Assert; - -/** - * Redis implementation of the key/value style - * {@link org.springframework.integration.store.MessageStore} and - * {@link org.springframework.integration.store.MessageGroupStore} - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class RedisMessageStore extends AbstractKeyValueMessageStore implements BeanClassLoaderAware { - - private static final String ID_MUST_NOT_BE_NULL = "'id' must not be null"; - - private final RedisTemplate redisTemplate; - - private boolean valueSerializerSet; - - private volatile boolean unlinkAvailable = true; - - /** - * Construct {@link RedisMessageStore} based on the provided - * {@link RedisConnectionFactory} and default empty prefix. - * @param connectionFactory the RedisConnectionFactory to use - */ - public RedisMessageStore(RedisConnectionFactory connectionFactory) { - this(connectionFactory, ""); - } - - /** - * Construct {@link RedisMessageStore} based on the provided - * {@link RedisConnectionFactory} and prefix. - * @param connectionFactory the RedisConnectionFactory to use - * @param prefix the key prefix to use, allowing the same broker to be used for - * multiple stores. - * @since 4.3.12 - * @see AbstractKeyValueMessageStore#AbstractKeyValueMessageStore(String) - */ - public RedisMessageStore(RedisConnectionFactory connectionFactory, String prefix) { - super(prefix); - this.redisTemplate = new RedisTemplate<>(); - this.redisTemplate.setConnectionFactory(connectionFactory); - this.redisTemplate.setKeySerializer(new StringRedisSerializer()); - this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - this.redisTemplate.afterPropertiesSet(); - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - if (!this.valueSerializerSet) { - this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer(classLoader)); - } - } - - public void setValueSerializer(RedisSerializer valueSerializer) { - Assert.notNull(valueSerializer, "'valueSerializer' must not be null"); - this.redisTemplate.setValueSerializer(valueSerializer); - this.valueSerializerSet = true; - } - - @Override - protected Object doRetrieve(Object id) { - Assert.notNull(id, ID_MUST_NOT_BE_NULL); - BoundValueOperations ops = this.redisTemplate.boundValueOps(id); - return ops.get(); - } - - @Override - protected void doStore(Object id, Object objectToStore) { - Assert.notNull(id, ID_MUST_NOT_BE_NULL); - Assert.notNull(objectToStore, "'objectToStore' must not be null"); - BoundValueOperations ops = this.redisTemplate.boundValueOps(id); - try { - ops.set(objectToStore); - } - catch (SerializationException e) { - rethrowAsIllegalArgumentException(e); - - } - } - - @Override - protected void doStoreIfAbsent(Object id, Object objectToStore) { - Assert.notNull(id, ID_MUST_NOT_BE_NULL); - Assert.notNull(objectToStore, "'objectToStore' must not be null"); - BoundValueOperations ops = this.redisTemplate.boundValueOps(id); - try { - Boolean present = ops.setIfAbsent(objectToStore); - if (present != null && logger.isDebugEnabled()) { - logger.debug("The message: [" + present + "] is already present in the store. " + - "The [" + objectToStore + "] is ignored."); - } - } - catch (SerializationException e) { - rethrowAsIllegalArgumentException(e); - } - } - - @Override - protected Object doRemove(Object id) { - Assert.notNull(id, ID_MUST_NOT_BE_NULL); - Object removedObject = this.doRetrieve(id); - if (removedObject != null) { - if (this.unlinkAvailable) { - try { - this.redisTemplate.unlink(id); - } - catch (Exception ex) { - unlinkUnavailable(ex); - this.redisTemplate.delete(id); - } - } - else { - this.redisTemplate.delete(id); - } - } - return removedObject; - } - - private void unlinkUnavailable(Exception ex) { - if (logger.isDebugEnabled()) { - logger.debug("The UNLINK command has failed (not supported on the Redis server?); " + - "falling back to the regular DELETE command", ex); - } - else { - logger.warn("The UNLINK command has failed (not supported on the Redis server?); " + - "falling back to the regular DELETE command: " + ex.getMessage()); - } - this.unlinkAvailable = false; - } - - @Override - protected void doRemoveAll(Collection ids) { - if (this.unlinkAvailable) { - try { - this.redisTemplate.unlink(ids); - } - catch (Exception ex) { - unlinkUnavailable(ex); - this.redisTemplate.delete(ids); - } - } - else { - this.redisTemplate.delete(ids); - } - } - - @Override - protected Collection doListKeys(String keyPattern) { - Assert.hasText(keyPattern, "'keyPattern' must not be empty"); - return this.redisTemplate.keys(keyPattern); - } - - private void rethrowAsIllegalArgumentException(SerializationException e) { - throw new IllegalArgumentException("If relying on the default RedisSerializer " + - "(JdkSerializationRedisSerializer) the Object must be Serializable. " + - "Either make it Serializable or provide your own implementation of " + - "RedisSerializer via 'setValueSerializer(..)'", e); - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/package-info.java deleted file mode 100644 index 96a0aa16d3f..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to the Redis message store. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.store; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/support/RedisHeaders.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/support/RedisHeaders.java deleted file mode 100644 index d65a834e260..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/support/RedisHeaders.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.support; - -/** - * Pre-defined names and prefixes to be used for - * for dealing with headers required by Redis components - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Attoumane Ahamadi - * - * @since 2.2 - */ -public final class RedisHeaders { - - private RedisHeaders() { - } - - public static final String PREFIX = "redis_"; - - public static final String KEY = PREFIX + "key"; - - public static final String MAP_KEY = PREFIX + "mapKey"; - - public static final String ZSET_SCORE = PREFIX + "zsetScore"; - - public static final String ZSET_INCREMENT_SCORE = PREFIX + "zsetIncrementScore"; - - public static final String COMMAND = PREFIX + "command"; - - public static final String MESSAGE_SOURCE = PREFIX + "messageSource"; - - public static final String STREAM_KEY = PREFIX + "streamKey"; - - public static final String STREAM_MESSAGE_ID = PREFIX + "streamMessageId"; - - public static final String CONSUMER_GROUP = PREFIX + "consumerGroup"; - - public static final String CONSUMER = PREFIX + "consumer"; - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/support/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/support/package-info.java deleted file mode 100644 index bdf89d53238..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/support/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides supporting classes for this module. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.support; diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java deleted file mode 100644 index e8abf03b5ca..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java +++ /dev/null @@ -1,913 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.util; - -import java.io.Serial; -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.util.Collections; -import java.util.ConcurrentModificationException; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.dao.CannotAcquireLockException; -import org.springframework.data.redis.connection.Message; -import org.springframework.data.redis.connection.MessageListener; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.core.script.DefaultRedisScript; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.listener.Topic; -import org.springframework.integration.support.locks.DistributedLock; -import org.springframework.integration.support.locks.ExpirableLockRegistry; -import org.springframework.integration.support.locks.RenewableLockRegistry; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.CustomizableThreadFactory; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * Implementation of {@link ExpirableLockRegistry} providing a distributed lock using Redis. - * Locks are stored under the key {@code registryKey:lockKey}. Locks expire after - * (default 60) seconds. Threads unlocking an - * expired lock will get an {@link IllegalStateException}. This should be - * considered as a critical error because it is possible the protected - * resources were compromised. - *

- * Locks are reentrant. - *

- * However, locks are scoped by the registry; a lock from a different registry with the - * same key (even if the registry uses the same 'registryKey') are different - * locks, and the second cannot be acquired by the same thread while the first is - * locked. - *

- * Note: This is not intended for low latency applications. It is intended - * for resource locking across multiple JVMs. - *

- * {@link Condition}s are not supported. - * - * @author Gary Russell - * @author Konstantin Yakimov - * @author Artem Bilan - * @author Vedran Pavic - * @author Unseok Kim - * @author Anton Gabov - * @author Christian Tzolov - * @author Eddie Cho - * @author Myeonghyeon Lee - * @author Roman Zabaluev - * @author Alex Peelman - * @author Youbin Wu - * - * @since 4.0 - * - */ -public final class RedisLockRegistry - implements ExpirableLockRegistry, DisposableBean, RenewableLockRegistry { - - private static final Log LOGGER = LogFactory.getLog(RedisLockRegistry.class); - - private static final long DEFAULT_EXPIRE_AFTER = 60000L; - - private static final int DEFAULT_CAPACITY = 100_000; - - private static final int DEFAULT_IDLE = 100; - - private final Lock lock = new ReentrantLock(); - - private Duration idleBetweenTries = Duration.ofMillis(DEFAULT_IDLE); - - private final Map locks = - new LinkedHashMap<>(16, 0.75F, true) { - - @Serial - private static final long serialVersionUID = 7419938441348450459L; - - @Override - protected boolean removeEldestEntry(Entry eldest) { - return size() > RedisLockRegistry.this.cacheCapacity; - } - - }; - - private final String clientId = UUID.randomUUID().toString(); - - private final String registryKey; - - private final String unLockChannelKey; - - private final StringRedisTemplate redisTemplate; - - private final Duration expireAfter; - - private int cacheCapacity = DEFAULT_CAPACITY; - - private RedisLockType redisLockType = RedisLockType.SPIN_LOCK; - - /** - * An {@link ExecutorService} to call {@link StringRedisTemplate#delete} in - * the separate thread when the current one is interrupted. - */ - @SuppressWarnings("NullAway.Init") - private Executor executor = - Executors.newCachedThreadPool(new CustomizableThreadFactory("redis-lock-registry-")); - - private @Nullable TaskScheduler renewalTaskScheduler; - - /** - * Flag to denote whether the {@link ExecutorService} was provided via the setter and - * thus should not be shutdown when {@link #destroy()} is called - */ - private boolean executorExplicitlySet; - - private volatile boolean unlinkAvailable = true; - - private volatile boolean isRunningRedisMessageListenerContainer = false; - - /** - * It is set via lazy initialization when it is a {@link RedisLockType#PUB_SUB_LOCK}. - */ - private volatile RedisPubSubLock.@Nullable RedisUnLockNotifyMessageListener unlockNotifyMessageListener; - - /** - * It is set via lazy initialization when it is a {@link RedisLockType#PUB_SUB_LOCK}. - */ - private volatile @Nullable RedisMessageListenerContainer redisMessageListenerContainer; - - /** - * Create a lock registry with the default (60 second) lock expiration. - * @param connectionFactory The connection factory. - * @param registryKey The key prefix for locks. - */ - public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey) { - this(connectionFactory, registryKey, DEFAULT_EXPIRE_AFTER); - } - - /** - * Create a lock registry with the supplied lock expiration. - * @param connectionFactory The connection factory. - * @param registryKey The key prefix for locks. - * @param expireAfter The expiration in milliseconds. - */ - public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey, long expireAfter) { - this(connectionFactory, registryKey, Duration.ofMillis(expireAfter)); - } - - /** - * Create a lock registry with the supplied lock expiration. - * @param connectionFactory The connection factory. - * @param registryKey The key prefix for locks. - * @param expireAfter The expiration in {@link Duration}. - * @since 7.0 - */ - public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey, Duration expireAfter) { - Assert.notNull(connectionFactory, "'connectionFactory' cannot be null"); - Assert.notNull(registryKey, "'registryKey' cannot be null"); - this.redisTemplate = new StringRedisTemplate(connectionFactory); - this.registryKey = registryKey; - this.expireAfter = expireAfter; - this.unLockChannelKey = registryKey + "-channel"; - } - - private void setupUnlockMessageListener(RedisConnectionFactory connectionFactory) { - Assert.isNull(RedisLockRegistry.this.redisMessageListenerContainer, - "'redisMessageListenerContainer' must not have been re-initialized."); - Assert.isNull(RedisLockRegistry.this.unlockNotifyMessageListener, - "'unlockNotifyMessageListener' must not have been re-initialized."); - RedisLockRegistry.this.redisMessageListenerContainer = new RedisMessageListenerContainer(); - RedisLockRegistry.this.unlockNotifyMessageListener = new RedisPubSubLock.RedisUnLockNotifyMessageListener(); - final Topic topic = new ChannelTopic(this.unLockChannelKey); - RedisMessageListenerContainer container = RedisLockRegistry.this.redisMessageListenerContainer; - RedisPubSubLock.RedisUnLockNotifyMessageListener listener = RedisLockRegistry.this.unlockNotifyMessageListener; - container.setConnectionFactory(connectionFactory); - container.setTaskExecutor(this.executor); - container.setSubscriptionExecutor(this.executor); - container.addMessageListener(listener, topic); - } - - /** - * Set the {@link Executor}, where is not provided then a default of - * cached thread pool Executor will be used. - * @param executor the executor service - * @since 5.0.5 - */ - public void setExecutor(Executor executor) { - this.executor = executor; - this.executorExplicitlySet = true; - } - - @Override - public void setRenewalTaskScheduler(TaskScheduler renewalTaskScheduler) { - Assert.notNull(renewalTaskScheduler, "'renewalTaskScheduler' must not be null"); - this.renewalTaskScheduler = renewalTaskScheduler; - } - - /** - * Set the capacity of cached locks. - * @param cacheCapacity The capacity of cached lock, (default 100_000). - * @since 5.5.6 - */ - public void setCacheCapacity(int cacheCapacity) { - this.cacheCapacity = cacheCapacity; - } - - /** - * Specify a {@link Duration} to sleep between obtainLock attempts. - * Defaults to 100 milliseconds. - * @param idleBetweenTries the {@link Duration} to sleep between obtainLock attempts. - * @since 6.4.0 - */ - public void setIdleBetweenTries(Duration idleBetweenTries) { - Assert.notNull(idleBetweenTries, "'idleBetweenTries' must not be null"); - this.idleBetweenTries = idleBetweenTries; - } - - /** - * Set {@link RedisLockType} mode to work in. - * By default, the {@link RedisLockType#SPIN_LOCK} is used - works in all the environment. - * The {@link RedisLockType#PUB_SUB_LOCK} is a preferred mode when not in Master/Replica connections - - * less network chatter. - * Set the type of unlockType, Select the lock method. - * @param redisLockType the {@link RedisLockType} to work in. - * @since 5.5.13 - */ - public void setRedisLockType(RedisLockType redisLockType) { - Assert.notNull(redisLockType, "'redisLockType' cannot be null"); - this.redisLockType = redisLockType; - } - - @Override - public DistributedLock obtain(Object lockKey) { - Assert.isInstanceOf(String.class, lockKey); - String path = (String) lockKey; - this.lock.lock(); - try { - return this.locks.computeIfAbsent(path, getRedisLockConstructor(this.redisLockType)); - } - finally { - this.lock.unlock(); - } - } - - @Override - public void expireUnusedOlderThan(long age) { - long now = System.currentTimeMillis(); - this.lock.lock(); - try { - this.locks.entrySet() - .removeIf(entry -> { - RedisLock lock = entry.getValue(); - long lockedAt = lock.getLockedAt(); - return now - lockedAt > age - // 'lockedAt = 0' means that the lock is still not acquired! - && lockedAt > 0 - && !lock.isAcquiredInThisProcess(); - }); - } - finally { - this.lock.unlock(); - } - } - - @Override - public void destroy() { - if (!this.executorExplicitlySet) { - ((ExecutorService) this.executor).shutdown(); - } - if (this.redisMessageListenerContainer != null) { - try { - this.redisMessageListenerContainer.destroy(); - this.redisMessageListenerContainer = null; - this.isRunningRedisMessageListenerContainer = false; - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - } - - @Override - public void renewLock(Object lockKey) { - this.renewLock(lockKey, RedisLockRegistry.this.expireAfter); - } - - @Override - public void renewLock(Object lockKey, Duration ttl) { - String path = (String) lockKey; - RedisLock redisLock; - this.lock.lock(); - try { - redisLock = this.locks.computeIfAbsent(path, getRedisLockConstructor(this.redisLockType)); - } - finally { - this.lock.unlock(); - } - if (redisLock == null) { - throw new IllegalStateException("Could not renew mutex at " + path); - } - - if (!redisLock.renew(ttl.toMillis())) { - throw new IllegalStateException("Could not renew mutex at " + path); - } - } - - /** - * The mode in which this registry is going to work with locks. - */ - public enum RedisLockType { - - /** - * The lock is acquired by periodically(idleBetweenTries property) checking whether the lock can be acquired. - */ - SPIN_LOCK, - - /** - * The lock is acquired by redis pub-sub subscription. - */ - PUB_SUB_LOCK - } - - private Function getRedisLockConstructor(RedisLockType redisLockType) { - return switch (redisLockType) { - case SPIN_LOCK -> RedisSpinLock::new; - case PUB_SUB_LOCK -> RedisPubSubLock::new; - }; - } - - private abstract class RedisLock implements DistributedLock { - - private static final String OBTAIN_LOCK_SCRIPT = """ - local lockClientId = redis.call('GET', KEYS[1]) - if lockClientId == ARGV[1] then - redis.call('PEXPIRE', KEYS[1], ARGV[2]) - return true - elseif not lockClientId then - redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2]) - return true - end - return false - """; - - private static final String RENEW_SCRIPT = """ - if (redis.call('GET', KEYS[1]) == ARGV[1]) then - redis.call('PEXPIRE', KEYS[1], ARGV[2]) - return true - end - return false - """; - - protected static final RedisScript OBTAIN_LOCK_REDIS_SCRIPT = - new DefaultRedisScript<>(OBTAIN_LOCK_SCRIPT, Boolean.class); - - public static final RedisScript RENEW_REDIS_SCRIPT = - new DefaultRedisScript<>(RENEW_SCRIPT, Boolean.class); - - protected final String lockKey; - - private final ReentrantLock localLock = new ReentrantLock(); - - private volatile long lockedAt; - - private volatile @Nullable ScheduledFuture renewFuture; - - private RedisLock(String path) { - this.lockKey = constructLockKey(path); - } - - private String constructLockKey(String path) { - return RedisLockRegistry.this.registryKey + ':' + path; - } - - public long getLockedAt() { - return this.lockedAt; - } - - /** - * Attempt to acquire a lock in redis. - * @param time the maximum time(milliseconds) to wait for the lock, -1 infinity - * @param expireAfter the time-to-live(milliseconds) for the lock status data - * @return true if the lock was acquired and false if the waiting time elapsed before the lock was acquired - * @throws InterruptedException – - * if the current thread is interrupted while acquiring the lock (and interruption of lock acquisition is supported) - */ - protected abstract boolean tryRedisLockInner(long time, long expireAfter) - throws ExecutionException, InterruptedException; - - /** - * Unlock the lock using the unlink method in redis. - */ - protected abstract boolean removeLockKeyInnerUnlink(); - - /** - * Unlock the lock using the delete method in redis. - */ - protected abstract boolean removeLockKeyInnerDelete(); - - @Override - public final void lock() { - this.lock(RedisLockRegistry.this.expireAfter); - } - - @Override - public void lock(Duration ttl) { - this.localLock.lock(); - while (true) { - try { - if (tryRedisLock(-1L, ttl.toMillis())) { - return; - } - } - catch (InterruptedException e) { - /* - * This method must be uninterruptible so catch and ignore - * interrupts and only break out of the while loop when - * we get the lock. - */ - } - catch (Exception e) { - this.localLock.unlock(); - rethrowAsLockException(e); - } - } - } - - private void rethrowAsLockException(Exception e) { - throw new CannotAcquireLockException("Failed to lock mutex at " + this.lockKey, e); - } - - @Override - public final void lockInterruptibly() throws InterruptedException { - this.localLock.lockInterruptibly(); - while (true) { - try { - if (tryRedisLock(-1L, RedisLockRegistry.this.expireAfter.toMillis())) { - return; - } - } - catch (InterruptedException ie) { - this.localLock.unlock(); - Thread.currentThread().interrupt(); - throw ie; - } - catch (Exception e) { - this.localLock.unlock(); - rethrowAsLockException(e); - } - } - } - - @Override - public final boolean tryLock() { - try { - return tryLock(0, TimeUnit.MILLISECONDS); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } - } - - @Override - public final boolean tryLock(long time, TimeUnit unit) throws InterruptedException { - return this.tryLock(Duration.of(time, unit.toChronoUnit()), RedisLockRegistry.this.expireAfter); - } - - @Override - public boolean tryLock(Duration waitTime, Duration ttl) throws InterruptedException { - if (!this.localLock.tryLock(waitTime.toMillis(), TimeUnit.MILLISECONDS)) { - return false; - } - try { - boolean acquired = tryRedisLock(waitTime.toMillis(), ttl.toMillis()); - if (!acquired) { - this.localLock.unlock(); - } - return acquired; - } - catch (Exception e) { - this.localLock.unlock(); - rethrowAsLockException(e); - } - return false; - } - - private boolean tryRedisLock(long time, long expireAfter) throws ExecutionException, InterruptedException { - final boolean acquired = tryRedisLockInner(time, expireAfter); - if (acquired) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Acquired lock; " + this); - } - this.lockedAt = System.currentTimeMillis(); - if (RedisLockRegistry.this.renewalTaskScheduler != null) { - Duration delay = Duration.ofMillis(expireAfter / 3); - this.renewFuture = - RedisLockRegistry.this.renewalTaskScheduler.scheduleWithFixedDelay(() -> - this.renew(expireAfter), delay); - } - } - return acquired; - } - - protected final Boolean obtainLock(long expireAfter) { - return RedisLockRegistry.this.redisTemplate - .execute(OBTAIN_LOCK_REDIS_SCRIPT, Collections.singletonList(this.lockKey), - RedisLockRegistry.this.clientId, - String.valueOf(expireAfter)); - } - - @Override - public final void unlock() { - if (!this.localLock.isHeldByCurrentThread()) { - throw new IllegalStateException("You do not own lock at " + this.lockKey); - } - if (this.localLock.getHoldCount() > 1) { - this.localLock.unlock(); - return; - } - try { - if (Thread.currentThread().isInterrupted()) { - RedisLockRegistry.this.executor.execute(this::removeLockKey); - } - else { - removeLockKey(); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Released lock; " + this); - } - } - catch (Exception e) { - ReflectionUtils.rethrowRuntimeException(e); - } - finally { - this.localLock.unlock(); - } - } - - private void removeLockKey() { - if (RedisLockRegistry.this.unlinkAvailable) { - Boolean unlinkResult = null; - try { - // Attempt to UNLINK the lock key; an exception indicates lack of UNLINK support - unlinkResult = removeLockKeyInnerUnlink(); - } - catch (Exception ex) { - RedisLockRegistry.this.unlinkAvailable = false; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("The UNLINK command has failed (not supported on the Redis server?); " + - "falling back to the regular DELETE command", ex); - } - else { - LOGGER.warn("The UNLINK command has failed (not supported on the Redis server?); " + - "falling back to the regular DELETE command: " + ex.getMessage()); - } - } - - if (Boolean.TRUE.equals(unlinkResult)) { - // Lock key successfully unlinked - stopRenew(); - return; - } - else if (Boolean.FALSE.equals(unlinkResult)) { - throw new ConcurrentModificationException("Lock was released in the store due to expiration. " + - "The integrity of data protected by this lock may have been compromised."); - } - } - if (!removeLockKeyInnerDelete()) { - throw new ConcurrentModificationException("Lock was released in the store due to expiration. " + - "The integrity of data protected by this lock may have been compromised."); - } - else { - stopRenew(); - } - } - - protected final boolean renew(long expireAfter) { - boolean res = Boolean.TRUE.equals(RedisLockRegistry.this.redisTemplate.execute( - RENEW_REDIS_SCRIPT, Collections.singletonList(this.lockKey), - RedisLockRegistry.this.clientId, String.valueOf(expireAfter))); - if (!res) { - stopRenew(); - } - return res; - } - - protected final void stopRenew() { - if (this.renewFuture != null) { - this.renewFuture.cancel(true); - this.renewFuture = null; - } - } - - @Override - public final Condition newCondition() { - throw new UnsupportedOperationException("Conditions are not supported"); - } - - public final boolean isAcquiredInThisProcess() { - return this.localLock.isLocked(); - } - - @Override - public String toString() { - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd@HH:mm:ss.SSS"); - return "RedisLock [lockKey=" + this.lockKey - + ",lockedAt=" + dateFormat.format(new Date(this.lockedAt)) - + ", clientId=" + RedisLockRegistry.this.clientId - + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + getOuterType().hashCode(); - result = prime * result + ((this.lockKey == null) ? 0 : this.lockKey.hashCode()); - result = prime * result + Long.hashCode(this.lockedAt); - result = prime * result + RedisLockRegistry.this.clientId.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - RedisLock other = (RedisLock) obj; - if (!getOuterType().equals(other.getOuterType())) { - return false; - } - if (!this.lockKey.equals(other.lockKey)) { - return false; - } - return this.lockedAt == other.lockedAt; - } - - private RedisLockRegistry getOuterType() { - return RedisLockRegistry.this; - } - - } - - private final class RedisPubSubLock extends RedisLock { - - private static final String UNLINK_UNLOCK_SCRIPT = """ - local lockClientId = redis.call('GET', KEYS[1]) - if (lockClientId == ARGV[1] and redis.call('UNLINK', KEYS[1]) == 1) then - redis.call('PUBLISH', ARGV[2], KEYS[1]) - return true - end - return false - """; - - private static final String DELETE_UNLOCK_SCRIPT = """ - local lockClientId = redis.call('GET', KEYS[1]) - if (lockClientId == ARGV[1] and redis.call('DEL', KEYS[1]) == 1) then - redis.call('PUBLISH', ARGV[2], KEYS[1]) - return true - end - return false - """; - - private static final RedisScript - UNLINK_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(UNLINK_UNLOCK_SCRIPT, Boolean.class); - - private static final RedisScript - DELETE_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(DELETE_UNLOCK_SCRIPT, Boolean.class); - - private RedisPubSubLock(String path) { - super(path); - } - - @Override - protected boolean tryRedisLockInner(long time, long expireAfter) - throws ExecutionException, InterruptedException { - return subscribeLock(time, expireAfter); - } - - @Override - protected boolean removeLockKeyInnerUnlink() { - return removeLockKeyWithScript(UNLINK_UNLOCK_REDIS_SCRIPT); - } - - @Override - protected boolean removeLockKeyInnerDelete() { - return removeLockKeyWithScript(DELETE_UNLOCK_REDIS_SCRIPT); - } - - private boolean removeLockKeyWithScript(RedisScript redisScript) { - return Boolean.TRUE.equals(RedisLockRegistry.this.redisTemplate.execute( - redisScript, Collections.singletonList(this.lockKey), - RedisLockRegistry.this.clientId, RedisLockRegistry.this.unLockChannelKey)); - } - - private boolean subscribeLock(long time, long expireAfter) throws ExecutionException, InterruptedException { - final long expiredTime = System.currentTimeMillis() + time; - if (obtainLock(expireAfter)) { - return true; - } - - if (!(RedisLockRegistry.this.isRunningRedisMessageListenerContainer - && RedisLockRegistry.this.redisMessageListenerContainer != null - && RedisLockRegistry.this.redisMessageListenerContainer.isRunning())) { - - runRedisMessageListenerContainer(); - } - while (time == -1 || expiredTime >= System.currentTimeMillis()) { - try { - Assert.state(RedisLockRegistry.this.unlockNotifyMessageListener != null, - "'unlockNotifyMessageListener' must be initialized"); - Future future = RedisLockRegistry.this.unlockNotifyMessageListener.subscribeLock(this.lockKey); - //DCL - if (obtainLock(expireAfter)) { - return true; - } - try { - //if short expireAfter key expire for ttl, no receive unlock msg - long waitTime = time >= 0 ? time : RedisLockRegistry.this.expireAfter.toMillis(); - future.get(waitTime, TimeUnit.MILLISECONDS); - } - catch (TimeoutException ignore) { - } - if (obtainLock(expireAfter)) { - return true; - } - } - finally { - if (RedisLockRegistry.this.unlockNotifyMessageListener != null) { - RedisLockRegistry.this.unlockNotifyMessageListener.unSubscribeLock(this.lockKey); - } - } - } - return false; - } - - private void runRedisMessageListenerContainer() { - RedisLockRegistry.this.lock.lock(); - try { - if (!(RedisLockRegistry.this.isRunningRedisMessageListenerContainer - && RedisLockRegistry.this.redisMessageListenerContainer != null - && RedisLockRegistry.this.redisMessageListenerContainer.isRunning())) { - - if (RedisLockRegistry.this.redisMessageListenerContainer == null) { - RedisConnectionFactory connectionFactory = RedisLockRegistry.this.redisTemplate.getConnectionFactory(); - Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); - setupUnlockMessageListener(connectionFactory); - RedisMessageListenerContainer container = RedisLockRegistry.this.redisMessageListenerContainer; - Assert.notNull(container, "RedisMessageListenerContainer must not be null after setup"); - container.afterPropertiesSet(); - } - - RedisMessageListenerContainer container = RedisLockRegistry.this.redisMessageListenerContainer; - Assert.notNull(container, "RedisMessageListenerContainer must not be null"); - container.start(); - RedisLockRegistry.this.isRunningRedisMessageListenerContainer = true; - } - } - finally { - RedisLockRegistry.this.lock.unlock(); - } - } - - private static final class RedisUnLockNotifyMessageListener implements MessageListener { - - private final Map> notifyMap = new ConcurrentHashMap<>(); - - @Override - public void onMessage(Message message, byte @Nullable [] pattern) { - final String lockKey = new String(message.getBody()); - unlockNotify(lockKey); - } - - public Future subscribeLock(String lockKey) { - return this.notifyMap.computeIfAbsent(lockKey, key -> new CompletableFuture<>()); - } - - public void unSubscribeLock(String localLock) { - this.notifyMap.remove(localLock); - } - - private void unlockNotify(String lockKey) { - this.notifyMap.computeIfPresent(lockKey, (key, lockFuture) -> { - lockFuture.complete(key); - return lockFuture; - }); - } - - } - - } - - private final class RedisSpinLock extends RedisLock { - - private static final String UNLINK_UNLOCK_SCRIPT = """ - local lockClientId = redis.call('GET', KEYS[1]) - if lockClientId == ARGV[1] then - redis.call('UNLINK', KEYS[1]) - return true - end - return false - """; - - private static final String DELETE_UNLOCK_SCRIPT = """ - local lockClientId = redis.call('GET', KEYS[1]) - if lockClientId == ARGV[1] then - redis.call('DEL', KEYS[1]) - return true - end - return false - """; - - private static final RedisScript - UNLINK_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(UNLINK_UNLOCK_SCRIPT, Boolean.class); - - private static final RedisScript - DELETE_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(DELETE_UNLOCK_SCRIPT, Boolean.class); - - private RedisSpinLock(String path) { - super(path); - } - - @Override - protected boolean tryRedisLockInner(long time, long expireAfter) throws InterruptedException { - long now = System.currentTimeMillis(); - if (time == -1L) { - while (!obtainLock(expireAfter)) { - Thread.sleep(RedisLockRegistry.this.idleBetweenTries.toMillis()); //NOSONAR - } - return true; - } - else { - long expire = now + TimeUnit.MILLISECONDS.convert(time, TimeUnit.MILLISECONDS); - boolean acquired; - while (!(acquired = obtainLock(expireAfter)) && System.currentTimeMillis() < expire) { //NOSONAR - Thread.sleep(RedisLockRegistry.this.idleBetweenTries.toMillis()); //NOSONAR - } - return acquired; - } - } - - @Override - protected boolean removeLockKeyInnerUnlink() { - return removeLockKeyWithScript(UNLINK_UNLOCK_REDIS_SCRIPT); - } - - @Override - protected boolean removeLockKeyInnerDelete() { - return removeLockKeyWithScript(DELETE_UNLOCK_REDIS_SCRIPT); - } - - private boolean removeLockKeyWithScript(RedisScript redisScript) { - return Boolean.TRUE.equals(RedisLockRegistry.this.redisTemplate.execute( - redisScript, Collections.singletonList(this.lockKey), - RedisLockRegistry.this.clientId)); - } - - } - -} diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/package-info.java deleted file mode 100644 index 66d447c00fd..00000000000 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides utility classes. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.redis.util; diff --git a/spring-integration-redis/src/main/resources/META-INF/spring.handlers b/spring-integration-redis/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index 74999c0ac86..00000000000 --- a/spring-integration-redis/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/redis=org.springframework.integration.redis.config.RedisNamespaceHandler \ No newline at end of file diff --git a/spring-integration-redis/src/main/resources/META-INF/spring.schemas b/spring-integration-redis/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index 0656ea0abdb..00000000000 --- a/spring-integration-redis/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,22 +0,0 @@ -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-2.1.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-2.2.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-3.0.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.0.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.1.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.2.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.3.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-5.0.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-5.1.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis-5.2.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -http\://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-2.1.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-2.2.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-3.0.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.0.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.1.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.2.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-4.3.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-5.0.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-5.1.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis-5.2.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd -https\://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd=org/springframework/integration/redis/config/spring-integration-redis.xsd diff --git a/spring-integration-redis/src/main/resources/META-INF/spring.tooling b/spring-integration-redis/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index 2dc2da493ec..00000000000 --- a/spring-integration-redis/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration redis namespace -http\://www.springframework.org/schema/integration/redis@name=integration redis Namespace -http\://www.springframework.org/schema/integration/redis@prefix=int-redis -http\://www.springframework.org/schema/integration/redis@icon=org/springframework/integration/redis/config/spring-integration-redis.gif diff --git a/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis.gif b/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis.gif deleted file mode 100644 index 351817f7b35..00000000000 Binary files a/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis.gif and /dev/null differ diff --git a/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis.xsd b/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis.xsd deleted file mode 100644 index 21963f70ee4..00000000000 --- a/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis.xsd +++ /dev/null @@ -1,1193 +0,0 @@ - - - - - - - - - - - - - - Defines a 'org.springframework.integration.redis.channel.SubscribableRedisChannel' that is backed by a - Redis topic. - - - - - - - - - Name of the Redis topic that backs this channel. - - - - - - - - - - - - - - - - - - - - - - - - - - - - ID for this channel. Required. - - - - - - - Reference to a RedisConnectionFactory. If none is provided, the default - bean name for the reference will be "redisConnectionFactory". - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Defines a Message Producing Endpoint for the - 'org.springframework.integration.redis.inbound.RedisInboundChannelAdapter' for subscribing to a Redis - channel. - - - - - - - - Reference to a RedisConnectionFactory. If none is provided, the default - bean name for the reference will be "redisConnectionFactory". - - - - - - - - - - - - Redis topic names as a comma-delimited list of Strings. - - - - - - - Redis topic patterns as a comma-delimited list of Strings. - - - - - - - - - - - - - - - - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer. - This attribute can be an empty string, which results in 'null' being used by the underlying - adapter, - meaning no serializer is used and the raw byte[] will be the message payload. - - - - - - - - - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.redis.outbound.RedisPublishingMessageHandler' that - a data to the Redis channel. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Reference to an instance of - org.springframework.data.redis.serializer.RedisSerializer - - - - - - - - - - - - - - - - Defines a Polling Channel Adapter for the - 'org.springframework.integration.redis.inbound.RedisStoreMessageSource' that creates a Message which - contains a view into a redis store. THe view could be one of the subclasses of - org.springframework.data.redis.support.collections.RedisStore - - - - - - - - - Collection type supported by this adapter - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.redis.outbound.RedisStoreWritingMessageHandler' - that writes the contents of the Message into - 'org.springframework.data.redis.support.collections.RedisStore' - - - - - - - - - - - - - - RedisTemplate to be used with this adapter - - - - - - - - - - - - Specifies the order for invocation when this adapter is connected as a - subscriber to a SubscribableChannel. - - - - - - - - - - - - Defines a Message Producing Endpoint for the - 'org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint' for listening a Redis - queue. - - - - - - - - - Redis queue name. - - - - - - - - - - - - - Reference to an instance of - org.springframework.data.redis.serializer.RedisSerializer. - It can be specified as an empty String value, which means the Endpoint's - 'serializer' property is - set to 'null', in which case the Message will contain the raw byte[] payload. - - - - - - - - - - - Specify the timeout in milliseconds to wait for the result of the - 'rightPop' operation on Redis queue. - Default is 1 second. - - - - - - - Specify the time in milliseconds for which the listener task should sleep after catching - an Exception on a Redis operation, before restarting the listener task. - Default is 5 seconds. - - - - - - - When true, specifies that the 'byte[]' from a Redis message should be deserialized - as an entire Spring Integration Message. Otherwise the data becomes just the - payload of the message (deserialized or not). - If this attribute is 'true', the 'serializer' must not be an empty String. - Default is 'false'. - - - - - - - When 'false', specifies that data is read using a 'left pop' operation instead of a - 'right pop'. - Default is 'true'. - - - - - - - - - - - - - - - - - - - - - - Defines a Messaging Gateway Endpoint for the - 'org.springframework.integration.redis.inbound.RedisQueueInboundGateway' that adapts incoming Redis - values to Spring Integration Messages and returns a reply. - - - - - - - Unique ID for this gateway. - - - - - - .reply' - to which this gateway will send the reply. - ]]> - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer. - It can be specified as an empty String value, which means the Endpoint's 'serializer' - property is set to 'null', in which case the Message will contain the raw byte[] payload. - - - - - - - - - - - Message Channel to which Messages should be sent in order to have them converted and published. - - - - - - - - - - - - - Message Channel to which replies for this gateway should be sent. - - - - - - - - - - - - - - - - - Reference to the Redis ConnectionFactory to be used by this component. - - - - - - - - - - - - Specify the timeout in milliseconds to wait for the result of the - 'rightPop' operation on Redis queue. - Default is 1 second. - - - - - - - Specify the time in milliseconds for which the listener task should sleep after catching - an Exception on a Redis operation, before restarting the listener task. - Default is 5 seconds. - - - - - - - - - - - - - - - - - When true, specifies that gateway deals only with the payload of the message. - Otherwise it expects the Redis 'value' to be a serialized 'Message'. - This option is applied to both the request and reply operations. - Defaults to 'true'. - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter' - to store data into the Redis List in a queue manner. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Reference to an instance of - org.springframework.data.redis.serializer.RedisSerializer - - - - - - - - - - - Specifies if the Message payload or the entire (serialized) Message will be send - to the Redis queue. - Default is 'true'. - - - - - - - When 'false', specifies that data is written using a 'right push' operation instead - of a 'left push'. - Default is 'true'. - - - - - - - - - - - .reply' as its key. A new UUID is used for each interaction. - ]]> - - - - - - - - - - - .reply'. - ]]> - - - - - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer - - - - - - - - - - - Specifies if the Message payload or the entire - (serialized) Message will be used as the Redis 'value'. - This option is applied for both request and reply operations. - Default is 'true'. - - - - - - - - - - - - Specify whether this outbound gateway must return a non-null value. This value is - 'true' by default, and a ReplyRequiredException will be thrown when - the underlying service returns a null value. - - - - - - - Message Channel where reply Messages will be sent. - - - - - - - Message Channel where request Messages will be expected. - - - - - - - Reference to the Redis ConnectionFactory to be used by this component. - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.redis.outbound.RedisOutboundGateway' that - perform a Redis command based on the 'command-expression' evaluation result. - - - - - - - - - - - - - Reference to a RedisConnectionFactory. If none is provided, the default - bean name for the reference will be "redisConnectionFactory". - Mutually exclusive with 'redis-template' attribute. - - - - - - - - - - - - RedisTemplate to be used with this gateway. - Mutually exclusive with 'connection-factory' attribute. - - - - - - - - - - - - - - - - - - - - - Specify whether this outbound gateway must return a non-null value. This value is - 'false' by default, otherwise a ReplyRequiredException will be thrown when - the underlying service returns a null value. - - - - - - - - - - - - - - - - - - - - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer. - Used to serialize each command argument to byte[] if necessary. - - - - - - - - - - - SpEL expression that returns the command key. Default is the 'redis_command' message header. - The command must not be evaluated to 'null'. - - - - - - - Comma-separated SpEL expressions that will be evaluated as command arguments. - Mutually exclusive with the 'arguments-strategy' attribute. If 'use-command-variable' is - specified to 'true', the '#cmd' variable will be presented within evaluation context. - Argument expressions may evaluate to 'null', to support a variable number of arguments. - - - - - - - Specifies, if the evaluated Redis command string will be made available - as the '#cmd' variable in the SpEL evaluation context in the - org.springframework.integration.redis.outbound.ExpressionArgumentsStrategy - when 'argument-expressions' is - configured, otherwise this attribute is ignored. - - - - - - - - Reference to an instance of - org.springframework.integration.redis.outbound.ArgumentsStrategy. - Mutually exclusive with the 'argument-expressions' attribute. By default the 'payload' is - used - as the command argument(s). To disable any strategy (no arguments) this attribute should be - configured as an empty string. - - - - - - - - - - - - - - Common configuration for Redis adapters. - - - - - - - - - - - - - - - - - - - - - - - - RedisTemplate to be used with this adapter - - - - - - - - - - - - SpEL expression that returns the name of the key for the collection being used. If you want - to provide a - constant key, use the 'key' attribute. - This attribute is mutually exclusive with the 'key' attribute. - - - - - - - The name of the key for the collection being used. If you require a key to be dynamically - determined per each poll use 'key-expression' attribute. - This attribute is mutually exclusive with the 'key-expression' attribute. - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer - to serialize the 'key' value for this collection. Please refer to the JavaDoc of the - RedisTemplate - for more detail. - - - - - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer - to serialize the 'value' entered into the collection. Please refer to the JavaDoc of the - RedisTemplate - for more detail. - - - - - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer - to be used when serializing hash keys (only relevant for hash-typed collections). - Please refer to the JavaDoc of the RedisTemplate for more detail. - - - - - - - - - - - - Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer - to be used when serializing hash values (only relevant for hash-typed collections). - Please refer to the JavaDoc of the RedisTemplate for more detail. - - - - - - - - - - - - Channel to which Error Messages will be sent. - - - - - - - - - - - - - - - - Provide the name of the key for the collection being used. If you require a key that - is determined dynamically for each message, use the 'key-expression' attribute. - The default key is dynamically determined from the 'redis_key' header. - This attribute is mutually exclusive with the 'key-expression' attribute. - - - - - - - SpEL expression that returns the name of the key for the collection being used. If you want to - provide a - constant key, use the 'key' attribute. Default is the 'redis_key' message header. - This attribute is mutually exclusive with the 'key' attribute. - - - - - - - SpEL expression that returns the name of the key for entry being stored. Only applies - if the 'collection-type' is MAP or PROPERTIES and 'extract-payload-elements' is false. - - - - - - - SpEL expression that returns the INCR flag for the ZADD command. Only applies - if the 'collection-type' is ZSET. - - - - - - - If set to 'true' (Default) and the payload is an instance of a "multi-value" object (i.e., - Collection or Map) - it will be stored using addAll/putAll semantics. Otherwise, if set to 'false' the payload will be - stored - as single entry regardless of its type. - If the payload is not an instance of a "multi-value" object, the value of this attribute is ignored - and - the payload will always be stored as a single entry. - - - - - - - Collection type supported by this adapter - - - - - - - - - - - - - - [DEFAULT] Redis List - - - - - - - Redis Set - - - - - - - Redis Sorted Set - - - - - - - Redis Map - - - - - - - Redis Properties - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/RedisContainerTest.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/RedisContainerTest.java deleted file mode 100644 index 47c370bfa16..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/RedisContainerTest.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis; - -import java.time.Duration; - -import io.lettuce.core.ClientOptions; -import io.lettuce.core.SocketOptions; -import org.junit.jupiter.api.BeforeAll; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.junit.jupiter.Testcontainers; - -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.BoundListOperations; -import org.springframework.data.redis.core.BoundZSetOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * The base contract for all tests requiring a Redis connection. - * The Testcontainers 'reuse' option must be disabled, so, Ryuk container is started - * and will clean all the containers up from this test suite after JVM exit. - * Since the Redis container instance is shared via static property, it is going to be - * started only once per JVM, therefore the target Docker container is reused automatically. - * - * @author Artem Vozhdayenko - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * - * @since 6.0 - */ -@Testcontainers(disabledWithoutDocker = true) -public interface RedisContainerTest { - - GenericContainer REDIS_CONTAINER = new GenericContainer<>("redis:7.0.2") - .withExposedPorts(6379); - - @BeforeAll - static void startContainer() { - REDIS_CONTAINER.start(); - } - - /** - * A primary method which should be used to connect to the test Redis instance. - * Can be used in any JUnit lifecycle methods if a test class implements this interface. - */ - static LettuceConnectionFactory connectionFactory() { - RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); - redisStandaloneConfiguration.setPort(REDIS_CONTAINER.getFirstMappedPort()); - - LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder() - .clientOptions( - ClientOptions.builder() - .socketOptions( - SocketOptions.builder() - .connectTimeout(Duration.ofMillis(10000)) - .keepAlive(true) - .build()) - .build()) - .commandTimeout(Duration.ofSeconds(10000)) - .build(); - - var connectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfiguration); - connectionFactory.afterPropertiesSet(); - return connectionFactory; - } - - static void awaitContainerSubscribed(RedisMessageListenerContainer container) throws Exception { - awaitContainerSubscribedNoWait(container); - } - - static void awaitContainerSubscribedNoWait(RedisMessageListenerContainer container) throws InterruptedException { - RedisConnection connection = null; - - int n = 0; - while (n++ < 300 && - (connection = - TestUtils.getPropertyValue(container, "subscriber.connection", RedisConnection.class)) - == null) { - - Thread.sleep(100); - } - assertThat(connection).as("RedisMessageListenerContainer Failed to Connect").isNotNull(); - - n = 0; - while (n++ < 300 && !connection.isSubscribed()) { - Thread.sleep(100); - } - assertThat(n < 300).as("RedisMessageListenerContainer Failed to Subscribe").isTrue(); - } - - static void awaitContainerSubscribedWithPatterns(RedisMessageListenerContainer container) throws Exception { - awaitContainerSubscribed(container); - RedisConnection connection = TestUtils.getPropertyValue(container, "subscriber.connection", - RedisConnection.class); - - int n = 0; - while (n++ < 300 && connection.getSubscription().getPatterns().size() == 0) { - Thread.sleep(100); - } - assertThat(n < 300).as("RedisMessageListenerContainer Failed to Subscribe with patterns").isTrue(); - // wait another second because of race condition - Thread.sleep(1000); - } - - static void awaitFullySubscribed(RedisMessageListenerContainer container, RedisTemplate redisTemplate, - String redisChannelName, QueueChannel channel, Object message) throws Exception { - awaitContainerSubscribedNoWait(container); - drain(channel); - long now = System.currentTimeMillis(); - Message received = null; - while (received == null && System.currentTimeMillis() - now < 30000) { - redisTemplate.convertAndSend(redisChannelName, message); - received = channel.receive(1000); - } - drain(channel); - assertThat(received).as("Container failed to fully start").isNotNull(); - } - - static void drain(QueueChannel channel) { - while (channel.receive(0) != null) { - // drain - } - } - - static void prepareList(RedisConnectionFactory connectionFactory) { - - StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory); - redisTemplate.delete("presidents"); - BoundListOperations ops = redisTemplate.boundListOps("presidents"); - - ops.rightPush("John Adams"); - - ops.rightPush("Barack Obama"); - ops.rightPush("Thomas Jefferson"); - ops.rightPush("John Quincy Adams"); - ops.rightPush("Zachary Taylor"); - - ops.rightPush("Theodore Roosevelt"); - ops.rightPush("Woodrow Wilson"); - ops.rightPush("George W. Bush"); - ops.rightPush("Franklin D. Roosevelt"); - ops.rightPush("Ronald Reagan"); - ops.rightPush("William J. Clinton"); - ops.rightPush("Abraham Lincoln"); - ops.rightPush("George Washington"); - } - - static void prepareZset(RedisConnectionFactory connectionFactory) { - - StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory); - - redisTemplate.delete("presidents"); - BoundZSetOperations ops = redisTemplate.boundZSetOps("presidents"); - - ops.add("John Adams", 18); - - ops.add("Barack Obama", 21); - ops.add("Thomas Jefferson", 19); - ops.add("John Quincy Adams", 19); - ops.add("Zachary Taylor", 19); - - ops.add("Theodore Roosevelt", 20); - ops.add("Woodrow Wilson", 20); - ops.add("George W. Bush", 21); - ops.add("Franklin D. Roosevelt", 20); - ops.add("Ronald Reagan", 20); - ops.add("William J. Clinton", 20); - ops.add("Abraham Lincoln", 19); - ops.add("George Washington", 18); - } - - static void deletePresidents(RedisConnectionFactory connectionFactory) { - deleteKey(connectionFactory, "presidents"); - } - - static void deleteKey(RedisConnectionFactory connectionFactory, String key) { - StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory); - redisTemplate.delete(key); - } - - static StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory connectionFactory) { - StringRedisTemplate redisTemplate = new StringRedisTemplate(); - redisTemplate.setConnectionFactory(connectionFactory); - redisTemplate.afterPropertiesSet(); - return redisTemplate; - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java deleted file mode 100644 index edbca7e2aee..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.channel; - -import java.lang.reflect.InvocationTargetException; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mock; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * @since 2.0 - */ -class SubscribableRedisChannelTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @Test - void pubSubChannelTest() throws Exception { - - SubscribableRedisChannel channel = new SubscribableRedisChannel(redisConnectionFactory, "si.test.channel"); - channel.setBeanFactory(mock(BeanFactory.class)); - channel.afterPropertiesSet(); - channel.start(); - - RedisContainerTest.awaitContainerSubscribed(TestUtils.getPropertyValue(channel, "container", - RedisMessageListenerContainer.class)); - - final CountDownLatch latch = new CountDownLatch(3); - MessageHandler handler = message -> latch.countDown(); - channel.subscribe(handler); - - channel.send(new GenericMessage("1")); - channel.send(new GenericMessage("2")); - channel.send(new GenericMessage("3")); - assertThat(latch.await(20, TimeUnit.SECONDS)).isTrue(); - } - - @Test - void dispatcherHasNoSubscribersTest() throws Exception { - - SubscribableRedisChannel channel = new SubscribableRedisChannel(redisConnectionFactory, "si.test.channel.no.subs"); - channel.setBeanName("dhnsChannel"); - channel.setBeanFactory(mock(BeanFactory.class)); - channel.afterPropertiesSet(); - - RedisMessageListenerContainer container = TestUtils.getPropertyValue( - channel, "container", RedisMessageListenerContainer.class); - @SuppressWarnings("unchecked") - Map> channelMapping = (Map>) TestUtils - .getPropertyValue(container, "channelMapping"); - MessageListenerAdapter listener = channelMapping.entrySet().iterator().next().getValue().iterator().next(); - Object delegate = TestUtils.getPropertyValue(listener, "delegate"); - try { - ReflectionUtils.findMethod(delegate.getClass(), "handleMessage", Object.class).invoke(delegate, - "Hello, world!"); - fail("Exception expected"); - } - catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - assertThat(cause).isNotNull(); - assertThat(cause.getMessage()) - .contains("Dispatcher has no subscribers for redis-channel 'si.test.channel.no.subs' (dhnsChannel)" + - "."); - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests-context.xml deleted file mode 100644 index 0ecf0305198..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests-context.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests.java deleted file mode 100644 index e166262de63..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.channel.SubscribableRedisChannel; -import org.springframework.integration.support.utils.IntegrationUtils; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Gunnar Hillert - * @author Artem Bilan - * @author Artem Vozhdayenko - */ -@SpringJUnitConfig -@DirtiesContext -class RedisChannelParserTests implements RedisContainerTest { - - @Autowired - private SubscribableRedisChannel redisChannel; - - @Autowired - private SubscribableRedisChannel redisChannelWithSubLimit; - - @Autowired - private ApplicationContext context; - - @BeforeEach - public void setup() { - this.redisChannel.start(); - this.redisChannelWithSubLimit.start(); - } - - @AfterEach - public void tearDown() { - this.redisChannel.stop(); - this.redisChannelWithSubLimit.stop(); - } - - @Test - void testPubSubChannelConfig() { - RedisConnectionFactory connectionFactory = - TestUtils.getPropertyValue(this.redisChannel, "connectionFactory", RedisConnectionFactory.class); - RedisSerializer redisSerializer = TestUtils.getPropertyValue(redisChannel, "serializer", - RedisSerializer.class); - assertThat(this.context.getBean("redisConnectionFactory")).isEqualTo(connectionFactory); - assertThat(this.context.getBean("redisSerializer")).isEqualTo(redisSerializer); - assertThat(TestUtils.getPropertyValue(redisChannel, "topicName")).isEqualTo("si.test.topic.parser"); - assertThat(TestUtils.getPropertyValue( - TestUtils.getPropertyValue(this.redisChannel, "dispatcher"), "maxSubscribers", Integer.class) - .intValue()).isEqualTo(Integer.MAX_VALUE); - - assertThat(TestUtils.getPropertyValue(this.redisChannelWithSubLimit, "dispatcher.maxSubscribers", - Integer.class) - .intValue()).isEqualTo(1); - Object mbf = this.context.getBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME); - assertThat(TestUtils.getPropertyValue(this.redisChannelWithSubLimit, "messageBuilderFactory")).isSameAs(mbf); - } - - @Test - void testPubSubChannelUsage() throws Exception { - RedisContainerTest.awaitContainerSubscribed(TestUtils.getPropertyValue(this.redisChannel, "container", - RedisMessageListenerContainer.class)); - - final Message m = new GenericMessage<>("Hello Redis"); - - final CountDownLatch latch = new CountDownLatch(1); - this.redisChannel.subscribe(message -> { - assertThat(message.getPayload()).isEqualTo(m.getPayload()); - latch.countDown(); - }); - - this.redisChannel.send(m); - - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml deleted file mode 100644 index 197345fdbaf..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java deleted file mode 100644 index 761dc843f80..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import java.util.concurrent.Executor; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.inbound.RedisInboundChannelAdapter; -import org.springframework.integration.support.converter.SimpleMessageConverter; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @author Gunnar Hillert - * @author Venil Noronha - * @author Artem Bilan - * @author Artem Vozhdayenko - */ -@SpringJUnitConfig -@DirtiesContext -class RedisInboundChannelAdapterParserTests implements RedisContainerTest { - - @Autowired - private ApplicationContext context; - - @Autowired - private MessageChannel autoChannel; - - @Autowired - @Qualifier("autoChannel.adapter") - private RedisInboundChannelAdapter autoChannelAdapter; - - @Autowired - private Executor executor; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - @Test - void validateConfiguration() { - RedisInboundChannelAdapter adapter = context.getBean("adapter", RedisInboundChannelAdapter.class); - assertThat(adapter.getComponentName()).isEqualTo("adapter"); - assertThat(adapter.getComponentType()).isEqualTo("redis:inbound-channel-adapter"); - DirectFieldAccessor accessor = new DirectFieldAccessor(adapter); - Object errorChannelBean = context.getBean("testErrorChannel"); - assertThat(accessor.getPropertyValue("errorChannel")).isEqualTo(errorChannelBean); - Object converterBean = context.getBean("testConverter"); - assertThat(accessor.getPropertyValue("messageConverter")).isEqualTo(converterBean); - assertThat(accessor.getPropertyValue("serializer")).isEqualTo(context.getBean("serializer")); - - Object container = accessor.getPropertyValue("container"); - DirectFieldAccessor containerAccessor = new DirectFieldAccessor(container); - assertThat(containerAccessor.getPropertyValue("taskExecutor")).isSameAs(this.executor); - - Object bean = context.getBean("withoutSerializer.adapter"); - assertThat(bean).isNotNull(); - assertThat(TestUtils.getPropertyValue(bean, "serializer")).isNull(); - } - - @Test - void testInboundChannelAdapterMessaging() throws Exception { - RedisInboundChannelAdapter adapter = context.getBean("adapter", RedisInboundChannelAdapter.class); - adapter.start(); - RedisContainerTest.awaitContainerSubscribedWithPatterns(TestUtils.getPropertyValue(adapter, "container", - RedisMessageListenerContainer.class)); - - redisConnectionFactory.getConnection().publish("foo".getBytes(), "Hello Redis from foo".getBytes()); - redisConnectionFactory.getConnection().publish("bar".getBytes(), "Hello Redis from bar".getBytes()); - - QueueChannel receiveChannel = context.getBean("receiveChannel", QueueChannel.class); - for (int i = 0; i < 3; i++) { - Message receive = receiveChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isIn("Hello Redis from foo", "Hello Redis from bar"); - } - - adapter.stop(); - } - - @Test - void testAutoChannel() { - assertThat(TestUtils.getPropertyValue(autoChannelAdapter, "outputChannel")).isSameAs(autoChannel); - } - - @SuppressWarnings("unused") - private static class TestMessageConverter extends SimpleMessageConverter { - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index fa8772c812d..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java deleted file mode 100644 index e592272b306..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.expression.Expression; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.inbound.RedisInboundChannelAdapter; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.converter.SimpleMessageConverter; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Artem Bilan - * @author Gunnar Hillert - * @author Gary Russell - * @author Artem Vozhdayenko - */ -@SpringJUnitConfig -@DirtiesContext -class RedisOutboundChannelAdapterParserTests implements RedisContainerTest { - - @Autowired - private ApplicationContext context; - - @Autowired - private RedisInboundChannelAdapter fooInbound; - - @Autowired - private RedisInboundChannelAdapter barInbound; - - @BeforeEach - void setup() { - this.fooInbound.start(); - this.barInbound.start(); - } - - @AfterEach - void tearDown() { - this.fooInbound.stop(); - this.barInbound.stop(); - } - - @Test - void validateConfiguration() { - EventDrivenConsumer adapter = context.getBean("outboundAdapter", EventDrivenConsumer.class); - Object handler = context.getBean("outboundAdapter.handler"); - - assertThat(adapter.getComponentName()).isEqualTo("outboundAdapter"); - DirectFieldAccessor accessor = new DirectFieldAccessor(handler); - Object topicExpression = accessor.getPropertyValue("topicExpression"); - assertThat(topicExpression).isNotNull(); - assertThat(((Expression) topicExpression).getExpressionString()).isEqualTo("headers['topic'] ?: 'foo'"); - Object converterBean = context.getBean("testConverter"); - assertThat(accessor.getPropertyValue("messageConverter")).isEqualTo(converterBean); - assertThat(accessor.getPropertyValue("serializer")).isEqualTo(context.getBean("serializer")); - - Object endpointHandler = TestUtils.getPropertyValue(adapter, "handler"); - - assertThat(AopUtils.isAopProxy(endpointHandler)).isTrue(); - - assertThat(((Advised) endpointHandler).getAdvisors()[0].getAdvice()) - .isInstanceOf(RequestHandlerRetryAdvice.class); - } - - @Test - void testOutboundChannelAdapterMessaging() throws Exception { - MessageChannel sendChannel = context.getBean("sendChannel", MessageChannel.class); - RedisContainerTest.awaitContainerSubscribed(TestUtils.getPropertyValue(fooInbound, "container", - RedisMessageListenerContainer.class)); - sendChannel.send(new GenericMessage<>("Hello Redis")); - QueueChannel receiveChannel = context.getBean("receiveChannel", QueueChannel.class); - Message message = receiveChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo("Hello Redis"); - - sendChannel = context.getBean("sendChannel", MessageChannel.class); - sendChannel.send(MessageBuilder.withPayload("Hello Redis").setHeader("topic", "bar").build()); - receiveChannel = context.getBean("barChannel", QueueChannel.class); - message = receiveChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo("Hello Redis"); - } - - @Test - //INT-2275 - void testOutboundChannelAdapterWithinChain() throws Exception { - MessageChannel sendChannel = context.getBean("redisOutboundChain", MessageChannel.class); - RedisContainerTest.awaitContainerSubscribed(TestUtils.getPropertyValue(fooInbound, "container", - RedisMessageListenerContainer.class)); - sendChannel.send(new GenericMessage<>("Hello Redis from chain")); - QueueChannel receiveChannel = context.getBean("receiveChannel", QueueChannel.class); - Message message = receiveChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo("Hello Redis from chain"); - } - - @SuppressWarnings("unused") - private static class TestMessageConverter extends SimpleMessageConverter { - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueGatewayIntegrationTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueGatewayIntegrationTests-context.xml deleted file mode 100644 index 18b8e528b19..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueGatewayIntegrationTests-context.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueGatewayIntegrationTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueGatewayIntegrationTests.java deleted file mode 100644 index 98707d16c19..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueGatewayIntegrationTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.inbound.RedisQueueInboundGateway; -import org.springframework.integration.redis.outbound.RedisQueueOutboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author David Liu - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 4.1 - */ -@SpringJUnitConfig -@DirtiesContext -class RedisQueueGatewayIntegrationTests implements RedisContainerTest { - - @Value("#{redisQueue.toString().bytes}") - private byte[] queueName; - - @Autowired - @Qualifier("sendChannel") - private DirectChannel sendChannel; - - @Autowired - @Qualifier("outputChannel") - private QueueChannel outputChannel; - - @Autowired - private RedisQueueInboundGateway inboundGateway; - - @Autowired - private RedisQueueOutboundGateway outboundGateway; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - @BeforeEach - void setup() { - redisConnectionFactory.getConnection().keyCommands().del(this.queueName); - this.inboundGateway.start(); - } - - @AfterEach - void tearDown() { - this.inboundGateway.stop(); - } - - @Test - void testRequestWithReply() { - this.sendChannel.send(new GenericMessage<>(1)); - Message receive = this.outputChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(2); - } - - @Test - void testInboundGatewayStop() { - Integer receiveTimeout = TestUtils.getPropertyValue(this.outboundGateway, "receiveTimeout", Integer.class); - this.outboundGateway.setReceiveTimeout(1); - this.inboundGateway.stop(); - try { - this.sendChannel.send(new GenericMessage<>("test1")); - } - catch (Exception e) { - assertThat(e.getMessage()).contains("No reply produced"); - } - finally { - this.outboundGateway.setReceiveTimeout(receiveTimeout); - } - } - - @Test - void testNullSerializer() { - Integer receiveTimeout = TestUtils.getPropertyValue(this.outboundGateway, "receiveTimeout", Integer.class); - this.outboundGateway.setReceiveTimeout(1); - this.inboundGateway.setSerializer(null); - try { - this.sendChannel.send(new GenericMessage<>("test1")); - } - catch (Exception e) { - assertThat(e.getMessage()).contains("No reply produced"); - } - finally { - this.inboundGateway.setSerializer(new StringRedisSerializer()); - this.outboundGateway.setReceiveTimeout(receiveTimeout); - } - } - - @Test - void testRequestReplyWithMessage() { - this.inboundGateway.setSerializer(new JdkSerializationRedisSerializer()); - this.inboundGateway.setExtractPayload(false); - this.outboundGateway.setSerializer(new JdkSerializationRedisSerializer()); - this.outboundGateway.setExtractPayload(false); - this.sendChannel.send(new GenericMessage<>(2)); - Message receive = this.outputChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(3); - this.inboundGateway.setSerializer(new StringRedisSerializer()); - this.inboundGateway.setExtractPayload(true); - this.outboundGateway.setSerializer(new StringRedisSerializer()); - this.outboundGateway.setExtractPayload(true); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParserTests-context.xml deleted file mode 100644 index ecc793aaccd..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParserTests.java deleted file mode 100644 index 7ee89e7c350..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParserTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.task.TaskExecutor; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundListOperations; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.integration.util.ErrorHandlingTaskExecutor; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @author Gary Russell - * @author Rainer Frey - * @author Matthias Jeschke - * - * @since 3.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class RedisQueueInboundChannelAdapterParserTests { - - @Autowired - @Qualifier("redisConnectionFactory") - private RedisConnectionFactory connectionFactory; - - @Autowired - @Qualifier("customRedisConnectionFactory") - private RedisConnectionFactory customRedisConnectionFactory; - - @Autowired - @Qualifier("defaultAdapter.adapter") - private RedisQueueMessageDrivenEndpoint defaultAdapter; - - @Autowired - @Qualifier("defaultAdapter") - private MessageChannel defaultAdapterChannel; - - @Autowired - @Qualifier("customAdapter") - private RedisQueueMessageDrivenEndpoint customAdapter; - - @Autowired - @Qualifier("zeroReceiveTimeoutAdapter") - private RedisQueueMessageDrivenEndpoint zeroReceiveTimeoutAdapter; - - @Autowired - @Qualifier("errorChannel") - private MessageChannel errorChannel; - - @Autowired - @Qualifier("sendChannel") - private MessageChannel sendChannel; - - @Autowired - @Qualifier("executor") - private TaskExecutor taskExecutor; - - @Autowired - private RedisSerializer serializer; - - @Test - @SuppressWarnings("unchecked") - public void testInt3017DefaultConfig() { - BoundListOperations boundListOperations = - TestUtils.getPropertyValue(this.defaultAdapter, "boundListOperations", BoundListOperations.class); - assertThat(boundListOperations.getKey()).isEqualTo("si.test.Int3017.Inbound1"); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "expectMessage", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "receiveTimeout")).isEqualTo(1000L); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "recoveryInterval")).isEqualTo(5000L); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "errorChannel")).isNull(); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "taskExecutor")) - .isInstanceOf(ErrorHandlingTaskExecutor.class); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "serializer")) - .isInstanceOf(JdkSerializationRedisSerializer.class); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "autoStartup", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "phase")).isEqualTo(Integer.MAX_VALUE / 2); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "outputChannel")) - .isSameAs(this.defaultAdapterChannel); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "rightPop", Boolean.class)).isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - public void testInt3017CustomConfig() { - BoundListOperations boundListOperations = - TestUtils.getPropertyValue(this.customAdapter, "boundListOperations", BoundListOperations.class); - assertThat(boundListOperations.getKey()).isEqualTo("si.test.Int3017.Inbound2"); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "expectMessage", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "receiveTimeout")).isEqualTo(2000L); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "recoveryInterval")).isEqualTo(3000L); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "errorChannel")).isSameAs(this.errorChannel); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "taskExecutor")).isSameAs(this.taskExecutor); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "serializer")).isSameAs(this.serializer); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "phase")).isEqualTo(100); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "outputChannel")).isSameAs(this.sendChannel); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "rightPop", Boolean.class)).isFalse(); - } - - @Test - public void testInt4341ZeroReceiveTimeoutConfig() { - assertThat(TestUtils.getPropertyValue(this.zeroReceiveTimeoutAdapter, "receiveTimeout")).isEqualTo(0L); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParserTests-context.xml deleted file mode 100644 index 0aa1764518a..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParserTests-context.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParserTests.java deleted file mode 100644 index cf890b2580a..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueInboundGatewayParserTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.integration.redis.inbound.RedisQueueInboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author David Liu - * @author Artem Bilan - * @author Matthias Jeschke - * - * since 4.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class RedisQueueInboundGatewayParserTests { - - @Autowired - @Qualifier("inboundGateway") - private RedisQueueInboundGateway defaultGateway; - - @Autowired - @Qualifier("zeroReceiveTimeoutGateway") - private RedisQueueInboundGateway zeroReceiveTimeoutGateway; - - @Autowired - @Qualifier("receiveChannel") - private MessageChannel receiveChannel; - - @Autowired - @Qualifier("requestChannel") - private MessageChannel requestChannel; - - @Autowired - private RedisSerializer serializer; - - @Test - public void testDefaultConfig() { - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "extractPayload", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "serializer")).isSameAs(this.serializer); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "serializerExplicitlySet", Boolean.class)).isTrue(); - assertThat(this.defaultGateway.getReplyChannel()).isSameAs(this.receiveChannel); - assertThat(this.defaultGateway.getRequestChannel()).isSameAs(this.requestChannel); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "messagingTemplate.receiveTimeout")).isEqualTo(2000L); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "taskExecutor")).isNotNull(); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "phase")).isEqualTo(3); - } - - @Test - public void testZeroReceiveTimeoutConfig() { - assertThat(TestUtils.getPropertyValue(this.zeroReceiveTimeoutGateway, "receiveTimeout")).isEqualTo(0L); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index fdcda25f9a1..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests.java deleted file mode 100644 index a2f3da7e19c..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.expression.Expression; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; -import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @author Gary Russell - * @author Rainer Frey - * - * @since 3.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class RedisQueueOutboundChannelAdapterParserTests { - - @Autowired - @Qualifier("redisConnectionFactory") - private RedisConnectionFactory connectionFactory; - - @Autowired - @Qualifier("customRedisConnectionFactory") - private RedisConnectionFactory customRedisConnectionFactory; - - @Autowired - @Qualifier("defaultAdapter") - private EventDrivenConsumer defaultEndpoint; - - @Autowired - @Qualifier("defaultAdapter.handler") - private RedisQueueOutboundChannelAdapter defaultAdapter; - - @Autowired - @Qualifier("customAdapter.handler") - private RedisQueueOutboundChannelAdapter customAdapter; - - @Autowired - private RedisSerializer serializer; - - @Test - public void testInt3017DefaultConfig() throws Exception { - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "template.connectionFactory")) - .isSameAs(this.connectionFactory); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "queueNameExpression", Expression.class) - .getExpressionString()).isEqualTo("foo"); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "extractPayload", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "serializerExplicitlySet", Boolean.class)).isFalse(); - - Object handler = TestUtils.getPropertyValue(this.defaultEndpoint, "handler"); - - assertThat(AopUtils.isAopProxy(handler)).isTrue(); - - assertThat(this.defaultAdapter).isSameAs(((Advised) handler).getTargetSource().getTarget()); - - assertThat(((Advised) handler).getAdvisors()[0].getAdvice()).isInstanceOf(RequestHandlerRetryAdvice.class); - assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "leftPush", Boolean.class)).isTrue(); - } - - @Test - public void testInt3017CustomConfig() { - assertThat(TestUtils.getPropertyValue(this.customAdapter, "template.connectionFactory")) - .isSameAs(this.customRedisConnectionFactory); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "queueNameExpression.expression")) - .isEqualTo("headers['redis_queue']"); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "extractPayload", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "serializerExplicitlySet", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "serializer")).isSameAs(this.serializer); - assertThat(TestUtils.getPropertyValue(this.customAdapter, "leftPush", Boolean.class)).isFalse(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParserTests-context.xml deleted file mode 100644 index cf270429b47..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParserTests-context.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParserTests.java deleted file mode 100644 index 7880fae0252..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundGatewayParserTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.integration.endpoint.PollingConsumer; -import org.springframework.integration.redis.outbound.RedisQueueOutboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author David Liu - * @author Gary Russell - * since 4.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class RedisQueueOutboundGatewayParserTests { - - @Autowired - @Qualifier("outboundGateway") - private PollingConsumer consumer; - - @Autowired - @Qualifier("outboundGateway.handler") - private RedisQueueOutboundGateway defaultGateway; - - @Autowired - @Qualifier("receiveChannel") - private MessageChannel receiveChannel; - - @Autowired - @Qualifier("requestChannel") - private MessageChannel requestChannel; - - @Autowired - private RedisSerializer serializer; - - @Test - public void testDefaultConfig() throws Exception { - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "extractPayload", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "serializer")).isSameAs(this.serializer); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "serializerExplicitlySet", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "order")).isEqualTo(2); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "outputChannel")).isSameAs(this.receiveChannel); - assertThat(TestUtils.getPropertyValue(this.consumer, "inputChannel")).isSameAs(this.requestChannel); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "requiresReply", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.defaultGateway, "receiveTimeout")).isEqualTo(2000); - assertThat(TestUtils.getPropertyValue(this.consumer, "autoStartup", Boolean.class)).isFalse(); - assertThat(TestUtils.getPropertyValue(this.consumer, "phase")).isEqualTo(3); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParserTests-context.xml deleted file mode 100644 index f9c76e8a448..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - LIST - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParserTests.java deleted file mode 100644 index d9a14106311..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParserTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.integration.redis.inbound.RedisStoreMessageSource; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Vozhdayenko - * @since 2.2 - */ -@SpringJUnitConfig -@DirtiesContext -class RedisStoreInboundChannelAdapterParserTests { - - @Autowired - private ApplicationContext context; - - @Autowired - private RedisTemplate redisTemplate; - - @Test - void validateWithStringTemplate() { - RedisStoreMessageSource withStringTemplate = - TestUtils.getPropertyValue(context.getBean("withStringTemplate"), "source", RedisStoreMessageSource.class); - assertThat(((SpelExpression) TestUtils.getPropertyValue(withStringTemplate, "keyExpression")) - .getExpressionString()).isEqualTo("'presidents'"); - assertThat(TestUtils.getPropertyValue(withStringTemplate, "collectionType")) - .hasToString("LIST"); - assertThat(TestUtils.getPropertyValue(withStringTemplate, "redisTemplate")) - .isInstanceOf(StringRedisTemplate.class); - } - - @Test - void validateWithExternalTemplate() { - RedisStoreMessageSource withExternalTemplate = - TestUtils.getPropertyValue(context.getBean("withExternalTemplate"), "source", RedisStoreMessageSource.class); - assertThat(((SpelExpression) TestUtils.getPropertyValue(withExternalTemplate, "keyExpression")) - .getExpressionString()).isEqualTo("'presidents'"); - assertThat((TestUtils.getPropertyValue(withExternalTemplate, "collectionType"))) - .hasToString("LIST"); - assertThat(TestUtils.getPropertyValue(withExternalTemplate, "redisTemplate")).isSameAs(redisTemplate); - } - - @Test - void testTemplateAndCfMutualExclusivity() { - assertThatThrownBy(() -> new ClassPathXmlApplicationContext("inbound-template-cf-fail.xml", this.getClass())) - .isInstanceOf(BeanDefinitionParsingException.class); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index 624804673e3..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - PROPERTIES - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParserTests.java deleted file mode 100644 index ddb67c52fa3..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisStoreOutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; -import org.springframework.integration.redis.outbound.RedisStoreWritingMessageHandler; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - */ -@SpringJUnitConfig -@DirtiesContext -public class RedisStoreOutboundChannelAdapterParserTests { - - @Autowired - private ApplicationContext context; - - @Autowired - private RedisTemplate redisTemplate; - - @Test - public void validateWithStringTemplate() throws Exception { - RedisStoreWritingMessageHandler withStringTemplate = context.getBean("withStringTemplate.handler", - RedisStoreWritingMessageHandler.class); - assertThat(((LiteralExpression) TestUtils.getPropertyValue(withStringTemplate, - "keyExpression")).getExpressionString()).isEqualTo("pepboys"); - assertThat((TestUtils.getPropertyValue(withStringTemplate, "collectionType")).toString()) - .isEqualTo("PROPERTIES"); - assertThat(TestUtils.getPropertyValue(withStringTemplate, "redisTemplate") instanceof StringRedisTemplate) - .isTrue(); - - Object handler = TestUtils.getPropertyValue(context.getBean("withStringTemplate.adapter"), "handler"); - - assertThat(AopUtils.isAopProxy(handler)).isTrue(); - - assertThat(withStringTemplate).isSameAs(((Advised) handler).getTargetSource().getTarget()); - - assertThat(((Advised) handler).getAdvisors()[0].getAdvice()).isInstanceOf(RequestHandlerRetryAdvice.class); - - assertThat(TestUtils.getPropertyValue(withStringTemplate, "zsetIncrementScoreExpression", - Expression.class).getExpressionString()).isEqualTo("true"); - } - - @Test - public void validateWithStringObjectTemplate() { - RedisStoreWritingMessageHandler withStringObjectTemplate = - TestUtils.getPropertyValue(context.getBean("withStringObjectTemplate.adapter"), "handler", - RedisStoreWritingMessageHandler.class); - assertThat(((LiteralExpression) TestUtils.getPropertyValue(withStringObjectTemplate, - "keyExpression")).getExpressionString()).isEqualTo("pepboys"); - assertThat((TestUtils.getPropertyValue(withStringObjectTemplate, "collectionType")).toString()) - .isEqualTo("PROPERTIES"); - assertThat(TestUtils.getPropertyValue(withStringObjectTemplate, "redisTemplate") instanceof StringRedisTemplate) - .isFalse(); - assertThat(TestUtils.getPropertyValue(withStringObjectTemplate, - "redisTemplate.keySerializer") instanceof StringRedisSerializer).isTrue(); - assertThat(TestUtils.getPropertyValue(withStringObjectTemplate, - "redisTemplate.hashKeySerializer") instanceof StringRedisSerializer).isTrue(); - assertThat(TestUtils.getPropertyValue(withStringObjectTemplate, - "redisTemplate.valueSerializer") instanceof JdkSerializationRedisSerializer).isTrue(); - assertThat(TestUtils.getPropertyValue(withStringObjectTemplate, - "redisTemplate.hashValueSerializer") instanceof JdkSerializationRedisSerializer).isTrue(); - } - - @Test - public void validateWithExternalTemplate() { - RedisStoreWritingMessageHandler withExternalTemplate = - TestUtils.getPropertyValue(context.getBean("withExternalTemplate.adapter"), "handler", - RedisStoreWritingMessageHandler.class); - assertThat(((LiteralExpression) TestUtils.getPropertyValue(withExternalTemplate, - "keyExpression")).getExpressionString()).isEqualTo("pepboys"); - assertThat((TestUtils.getPropertyValue(withExternalTemplate, "collectionType")).toString()) - .isEqualTo("PROPERTIES"); - assertThat(TestUtils.getPropertyValue(withExternalTemplate, "redisTemplate")).isSameAs(redisTemplate); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/inbound-template-cf-fail.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/inbound-template-cf-fail.xml deleted file mode 100644 index 0e19a44d32c..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/inbound-template-cf-fail.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/ReactiveRedisStreamMessageProducerTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/ReactiveRedisStreamMessageProducerTests.java deleted file mode 100644 index af207f24e61..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/ReactiveRedisStreamMessageProducerTests.java +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.time.Duration; -import java.util.Date; -import java.util.concurrent.atomic.AtomicReference; - -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.context.expression.MapAccessor; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.connection.stream.PendingMessagesSummary; -import org.springframework.data.redis.connection.stream.ReadOffset; -import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.stream.StreamReceiver; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.StaticMessageHeaderAccessor; -import org.springframework.integration.acks.SimpleAcknowledgment; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.outbound.ReactiveRedisStreamMessageHandler; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.redis.util.Address; -import org.springframework.integration.redis.util.Person; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -/** - * @author Attoumane Ahamadi - * @author Artem Bilan - * @author Rohan Mukesh - * @author Artem Vozhdayenko - * - * @since 5.4 - */ -@SpringJUnitConfig -@DirtiesContext -class ReactiveRedisStreamMessageProducerTests implements RedisContainerTest { - - private static final String STREAM_KEY = ReactiveRedisStreamMessageProducerTests.class.getSimpleName() + ".stream"; - - private static final String CONSUMER = ReactiveRedisStreamMessageProducerTests.class.getSimpleName() + ".consumer"; - - @Autowired - FluxMessageChannel fluxMessageChannel; - - @Autowired - ReactiveRedisStreamMessageProducer reactiveRedisStreamProducer; - - @Autowired - ReactiveRedisTemplate template; - - @Autowired - ReactiveMessageHandlerAdapter messageHandler; - - @Autowired - ReactiveRedisStreamMessageProducer reactiveErrorRedisStreamProducer; - - @Autowired - PollableChannel redisStreamErrorChannel; - - @BeforeEach - void delKey() { - this.template.delete(STREAM_KEY).block(); - } - - @AfterEach - void tearDown() { - this.reactiveRedisStreamProducer.stop(); - } - - @Test - void testConsumerGroupCreation() { - this.reactiveRedisStreamProducer.setCreateConsumerGroup(true); - this.reactiveRedisStreamProducer.setConsumerName(CONSUMER); - this.reactiveRedisStreamProducer.afterPropertiesSet(); - - Flux.from(this.fluxMessageChannel).subscribe(); - - this.reactiveRedisStreamProducer.start(); - - this.template.opsForStream() - .groups(STREAM_KEY) - .next() - .as(StepVerifier::create) - .assertNext((infoGroup) -> - assertThat(infoGroup.groupName()).isEqualTo(this.reactiveRedisStreamProducer.getBeanName())) - .thenCancel() - .verify(Duration.ofSeconds(10)); - } - - @Test - void testReadingMessageAsStandaloneClient() { - Address address = new Address("Rennes 3, France"); - Person person = new Person(address, "Attoumane"); - this.messageHandler.handleMessage(new GenericMessage<>(person)); - - this.reactiveRedisStreamProducer.setCreateConsumerGroup(false); - this.reactiveRedisStreamProducer.setConsumerName(null); - this.reactiveRedisStreamProducer.setReadOffset(ReadOffset.from("0-0")); - this.reactiveRedisStreamProducer.afterPropertiesSet(); - - StepVerifier stepVerifier = - Flux.from(this.fluxMessageChannel) - .as(StepVerifier::create) - .assertNext(message -> { - assertThat(message.getPayload()).isEqualTo(person); - assertThat(message.getHeaders()).containsKeys(RedisHeaders.STREAM_KEY, - RedisHeaders.STREAM_MESSAGE_ID); - }) - .thenCancel() - .verifyLater(); - - this.reactiveRedisStreamProducer.start(); - - stepVerifier.verify(Duration.ofSeconds(10)); - } - - @Test - void testReadingMessageAsConsumerInConsumerGroup() { - Address address = new Address("Winterfell, Westeros"); - Person person = new Person(address, "John Snow"); - - this.template.opsForStream() - .createGroup(STREAM_KEY, this.reactiveRedisStreamProducer.getBeanName()) - .as(StepVerifier::create) - .assertNext(message -> assertThat(message).isEqualTo("OK")) - .thenCancel() - .verify(Duration.ofSeconds(10)); - - this.reactiveRedisStreamProducer.setCreateConsumerGroup(false); - this.reactiveRedisStreamProducer.setConsumerName(CONSUMER); - this.reactiveRedisStreamProducer.setReadOffset(ReadOffset.latest()); - this.reactiveRedisStreamProducer.afterPropertiesSet(); - this.reactiveRedisStreamProducer.start(); - - StepVerifier stepVerifier = - Flux.from(this.fluxMessageChannel) - .as(StepVerifier::create) - .assertNext(message -> { - assertThat(message.getPayload()).isEqualTo(person); - assertThat(message.getHeaders()).containsKeys(RedisHeaders.CONSUMER_GROUP, RedisHeaders.CONSUMER); - }) - .thenCancel() - .verifyLater(); - - this.messageHandler.handleMessage(new GenericMessage<>(person)); - - stepVerifier.verify(Duration.ofSeconds(10)); - } - - @Test - void testReadingPendingMessageWithNoAutoACK() { - Address address = new Address("Winterfell, Westeros"); - Person person = new Person(address, "John Snow"); - - String consumerGroup = "testGroup"; - String consumerName = "testConsumer"; - - this.reactiveRedisStreamProducer.setCreateConsumerGroup(true); - this.reactiveRedisStreamProducer.setAutoAck(false); - this.reactiveRedisStreamProducer.setConsumerGroup(consumerGroup); - this.reactiveRedisStreamProducer.setConsumerName(consumerName); - this.reactiveRedisStreamProducer.setReadOffset(ReadOffset.latest()); - this.reactiveRedisStreamProducer.afterPropertiesSet(); - this.reactiveRedisStreamProducer.start(); - - AtomicReference acknowledgmentReference = new AtomicReference<>(); - - StepVerifier stepVerifier = - Flux.from(this.fluxMessageChannel) - .as(StepVerifier::create) - .assertNext(message -> { - assertThat(message.getPayload()).isEqualTo(person); - assertThat(message.getHeaders()) - .containsKeys(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK); - acknowledgmentReference.set(StaticMessageHeaderAccessor.getAcknowledgment(message)); - }) - .thenCancel() - .verifyLater(); - - this.messageHandler.handleMessage(new GenericMessage<>(person)); - - stepVerifier.verify(Duration.ofSeconds(10)); - - await().until(() -> - template.opsForStream() - .pending(STREAM_KEY, consumerGroup) - .block(Duration.ofMillis(100)) - .getTotalPendingMessages() == 1); - - acknowledgmentReference.get().acknowledge(); - - Mono pendingZeroMessage = - template.opsForStream().pending(STREAM_KEY, consumerGroup); - - StepVerifier.create(pendingZeroMessage) - .assertNext(pendingMessagesSummary -> - assertThat(pendingMessagesSummary.getTotalPendingMessages()).isZero()) - .verifyComplete(); - } - - @Test - void testReadingNextMessagesWhenSerializationException() { - Person person = new Person(new Address("Winterfell, Westeros"), "John Snow"); - Date testDate = new Date(); - this.reactiveErrorRedisStreamProducer.start(); - - StepVerifier stepVerifier = - Flux.from(this.fluxMessageChannel) - .map(Message::getPayload) - .cast(Date.class) - .as(StepVerifier::create) - .expectNext(testDate) - .thenCancel() - .verifyLater(); - - this.messageHandler.handleMessage(new GenericMessage<>(person)); - - Message errorMessage = this.redisStreamErrorChannel.receive(10_000); - assertThat(errorMessage).isInstanceOf(ErrorMessage.class) - .extracting("payload.message") - .asInstanceOf(InstanceOfAssertFactories.STRING) - .contains("Cannot deserialize Redis Stream Record"); - - Mono pendingMessage = - template.opsForStream() - .pending(STREAM_KEY, this.reactiveErrorRedisStreamProducer.getBeanName()); - - StepVerifier.create(pendingMessage) - .assertNext(pendingMessagesSummary -> - assertThat(pendingMessagesSummary.getTotalPendingMessages()).isEqualTo(1L)) - .verifyComplete(); - - Message failedMessage = ((MessagingException) errorMessage.getPayload()).getFailedMessage(); - StaticMessageHeaderAccessor.getAcknowledgment(failedMessage).acknowledge(); - - pendingMessage = - template.opsForStream() - .pending(STREAM_KEY, this.reactiveErrorRedisStreamProducer.getBeanName()); - - StepVerifier.create(pendingMessage) - .assertNext(pendingMessagesSummary -> - assertThat(pendingMessagesSummary.getTotalPendingMessages()).isZero()) - .verifyComplete(); - - this.messageHandler.handleMessage(new GenericMessage<>(testDate)); - - stepVerifier.verify(Duration.ofSeconds(10)); - - this.reactiveErrorRedisStreamProducer.stop(); - } - - @Configuration - static class ContextConfig { - - @Bean - ReactiveRedisConnectionFactory redisConnectionFactory() { - return RedisContainerTest.connectionFactory(); - } - - @Bean - ReactiveRedisStreamMessageHandler redisStreamMessageHandler( - ReactiveRedisConnectionFactory redisConnectionFactory) { - - return new ReactiveRedisStreamMessageHandler(redisConnectionFactory, STREAM_KEY); - } - - @Bean - public ReactiveMessageHandlerAdapter reactiveMessageHandlerAdapter( - ReactiveRedisConnectionFactory redisConnectionFactory) { - - return new ReactiveMessageHandlerAdapter(redisStreamMessageHandler(redisConnectionFactory)); - } - - @Bean - ReactiveRedisTemplate reactiveStreamOperations( - ReactiveRedisConnectionFactory redisConnectionFactory) { - - return new ReactiveRedisTemplate<>(redisConnectionFactory, - RedisSerializationContext.string()); - } - - @Bean - FluxMessageChannel fluxMessageChannel() { - return new FluxMessageChannel(); - } - - @Bean - PollableChannel redisStreamErrorChannel() { - return new QueueChannel(); - } - - @Bean - ReactiveRedisStreamMessageProducer reactiveErrorRedisStreamProducer( - ReactiveRedisConnectionFactory redisConnectionFactory) { - - ReactiveRedisStreamMessageProducer messageProducer = - new ReactiveRedisStreamMessageProducer(redisConnectionFactory, STREAM_KEY); - messageProducer.setTargetType(Date.class); - messageProducer.setPollTimeout(Duration.ofMillis(100)); - messageProducer.setCreateConsumerGroup(true); - messageProducer.setAutoAck(false); - messageProducer.setConsumerName("testConsumer"); - messageProducer.setReadOffset(ReadOffset.latest()); - messageProducer.setAutoStartup(false); - messageProducer.setOutputChannel(fluxMessageChannel()); - messageProducer.setErrorChannel(redisStreamErrorChannel()); - return messageProducer; - } - - @Bean - ReactiveRedisStreamMessageProducer reactiveRedisStreamProducer( - ReactiveRedisConnectionFactory redisConnectionFactory) { - - ReactiveRedisStreamMessageProducer messageProducer = - new ReactiveRedisStreamMessageProducer(redisConnectionFactory, STREAM_KEY); - messageProducer.setStreamReceiverOptions( - StreamReceiver.StreamReceiverOptions.builder() - .pollTimeout(Duration.ofMillis(100)) - .targetType(Person.class) - .build()); - messageProducer.setAutoStartup(false); - messageProducer.setOutputChannel(fluxMessageChannel()); - return messageProducer; - } - - @Bean - public StandardEvaluationContext integrationEvaluationContext(ApplicationContext applicationContext) { - StandardEvaluationContext integrationEvaluationContext = new StandardEvaluationContext(); - integrationEvaluationContext.addPropertyAccessor(new MapAccessor()); - integrationEvaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext)); - return integrationEvaluationContext; - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java deleted file mode 100644 index e350b7c6e6b..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 2.1 - */ -class RedisInboundChannelAdapterTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @Test - void testRedisInboundChannelAdapter() throws Exception { - for (int iteration = 0; iteration < 10; iteration++) { - testRedisInboundChannelAdapterGuts(iteration); - } - } - - private void testRedisInboundChannelAdapterGuts(int iteration) throws Exception { - int numToTest = 10; - String redisChannelName = "testRedisInboundChannelAdapterChannel"; - QueueChannel channel = new QueueChannel(); - - RedisConnectionFactory connectionFactory = redisConnectionFactory; - - RedisInboundChannelAdapter adapter = new RedisInboundChannelAdapter(connectionFactory); - adapter.setTopics(redisChannelName); - adapter.setOutputChannel(channel); - adapter.setBeanFactory(mock(BeanFactory.class)); - adapter.afterPropertiesSet(); - adapter.start(); - - StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); - redisTemplate.afterPropertiesSet(); - - RedisContainerTest.awaitFullySubscribed(TestUtils.getPropertyValue(adapter, "container", RedisMessageListenerContainer.class), - redisTemplate, redisChannelName, channel, "foo"); - - for (int i = 0; i < numToTest; i++) { - String message = "test-" + i + " iteration " + iteration; - redisTemplate.convertAndSend(redisChannelName, message); - } - int counter = 0; - for (int i = 0; i < numToTest; i++) { - Message message = channel.receive(10000); - if (message == null) { - throw new RuntimeException("Failed to receive message # " + i + " iteration " + iteration); - } - assertThat(message).isNotNull(); - assertThat(message.getPayload().toString()).startsWith("test-"); - assertThat(message.getHeaders()) - .containsEntry(RedisHeaders.MESSAGE_SOURCE, "testRedisInboundChannelAdapterChannel"); - counter++; - } - assertThat(counter).isEqualTo(numToTest); - adapter.stop(); - - redisChannelName = "testRedisBytesInboundChannelAdapterChannel"; - - adapter.setTopics(redisChannelName); - adapter.setSerializer(null); - adapter.afterPropertiesSet(); - adapter.start(); - - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - template.setEnableDefaultSerializer(false); - template.afterPropertiesSet(); - - RedisContainerTest.awaitFullySubscribed(TestUtils.getPropertyValue(adapter, "container", RedisMessageListenerContainer.class), - template, redisChannelName, channel, "foo".getBytes()); - - for (int i = 0; i < numToTest; i++) { - String message = "test-" + i + " iteration " + iteration; - template.convertAndSend(redisChannelName, message.getBytes()); - } - - counter = 0; - for (int i = 0; i < numToTest; i++) { - Message message = channel.receive(10000); - if (message == null) { - throw new RuntimeException("Failed to receive message # " + i + " iteration " + iteration); - } - assertThat(message).isNotNull(); - Object payload = message.getPayload(); - assertThat(payload).isInstanceOf(byte[].class); - - assertThat(new String((byte[]) payload)).startsWith("test-"); - counter++; - } - - assertThat(counter).isEqualTo(numToTest); - adapter.stop(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests-context.xml deleted file mode 100644 index 9c01721f71d..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests-context.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java deleted file mode 100644 index 6a3512fd4ef..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.SmartLifecycle; -import org.springframework.data.redis.RedisConnectionFailureException; -import org.springframework.data.redis.RedisSystemException; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundListOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.events.IntegrationEvent; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.event.RedisExceptionEvent; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; - -/** - * @author Gunnar Hillert - * @author Artem Bilan - * @author Gary Russell - * @author Rainer Frey - * @author Artem Vozhdayenko - * - * @since 3.0 - */ -@SpringJUnitConfig -@DirtiesContext -class RedisQueueMessageDrivenEndpointTests implements RedisContainerTest { - - public static final String TEST_QUEUE = UUID.randomUUID().toString(); - - @Autowired - private RedisConnectionFactory connectionFactory; - - @Autowired - private PollableChannel fromChannel; - - @Autowired - private SmartLifecycle fromChannelEndpoint; - - @Autowired - private MessageChannel symmetricalInputChannel; - - @Autowired - private SmartLifecycle symmetricalRedisChannelEndpoint; - - @Autowired - private PollableChannel symmetricalOutputChannel; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - @BeforeEach - void setUpTearDown() { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.afterPropertiesSet(); - redisTemplate.delete(TEST_QUEUE); - } - - @Test - @SuppressWarnings("unchecked") - void testInt3014Default() throws InterruptedException { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.setEnableDefaultSerializer(false); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate.afterPropertiesSet(); - - String payload = "testing"; - - redisTemplate.boundListOps(TEST_QUEUE).leftPush(payload); - - Date payload2 = new Date(); - - redisTemplate.boundListOps(TEST_QUEUE).leftPush(payload2); - - PollableChannel channel = new QueueChannel(); - - RedisQueueMessageDrivenEndpoint endpoint = - new RedisQueueMessageDrivenEndpoint(TEST_QUEUE, this.connectionFactory); - endpoint.setBeanFactory(Mockito.mock(BeanFactory.class)); - endpoint.setBeanClassLoader(ClassUtils.getDefaultClassLoader()); - endpoint.setOutputChannel(channel); - endpoint.setReceiveTimeout(10); - endpoint.afterPropertiesSet(); - endpoint.start(); - - Message receive = (Message) channel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload); - - receive = (Message) channel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload2); - - CountDownLatch stopLatch = new CountDownLatch(1); - endpoint.stop(stopLatch::countDown); - assertThat(stopLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - void testInt3014ExpectMessageTrue() throws InterruptedException { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.setEnableDefaultSerializer(false); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate.afterPropertiesSet(); - - Message message = MessageBuilder.withPayload("testing").build(); - - redisTemplate.boundListOps(TEST_QUEUE).leftPush(message); - - redisTemplate.boundListOps(TEST_QUEUE).leftPush("test"); - - PollableChannel channel = new QueueChannel(); - - PollableChannel errorChannel = new QueueChannel(); - - RedisQueueMessageDrivenEndpoint endpoint = - new RedisQueueMessageDrivenEndpoint(TEST_QUEUE, this.connectionFactory); - endpoint.setBeanFactory(Mockito.mock(BeanFactory.class)); - endpoint.setExpectMessage(true); - endpoint.setSerializer(new JdkSerializationRedisSerializer()); - endpoint.setOutputChannel(channel); - endpoint.setErrorChannel(errorChannel); - endpoint.setReceiveTimeout(10); - endpoint.afterPropertiesSet(); - endpoint.start(); - - Message receive = (Message) channel.receive(10000); - assertThat(receive) - .isNotNull() - .isEqualTo(message); - - receive = (Message) errorChannel.receive(10000); - assertThat(receive) - .isNotNull() - .isInstanceOf(ErrorMessage.class); - assertThat(receive.getPayload()).isInstanceOf(MessagingException.class); - assertThat(((Exception) receive.getPayload()).getMessage()).contains("Deserialization of Message failed."); - assertThat(((Exception) receive.getPayload()).getCause()).isInstanceOf(ClassCastException.class); - assertThat(((Exception) receive.getPayload()).getCause().getMessage()) - .contains("java.lang.String cannot be cast"); - - CountDownLatch stopLatch = new CountDownLatch(1); - endpoint.stop(stopLatch::countDown); - assertThat(stopLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - void testInt3017IntegrationInbound() throws InterruptedException { - this.fromChannelEndpoint.start(); - String payload = new Date().toString(); - - RedisTemplate redisTemplate = new StringRedisTemplate(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.afterPropertiesSet(); - - redisTemplate.boundListOps(TEST_QUEUE) - .leftPush("{\"payload\":\"" + payload + "\",\"headers\":{}}"); - - Message receive = this.fromChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload); - CountDownLatch stopLatch = new CountDownLatch(1); - this.fromChannelEndpoint.stop(stopLatch::countDown); - assertThat(stopLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - void testInt3017IntegrationSymmetrical() throws InterruptedException { - this.symmetricalRedisChannelEndpoint.start(); - UUID payload = UUID.randomUUID(); - Message message = MessageBuilder.withPayload(payload) - .setHeader("redis_queue", TEST_QUEUE) - .build(); - - this.symmetricalInputChannel.send(message); - - Message receive = this.symmetricalOutputChannel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload); - - CountDownLatch stopLatch = new CountDownLatch(1); - this.symmetricalRedisChannelEndpoint.stop(stopLatch::countDown); - assertThat(stopLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - void testInt3442ProperlyStop() throws Exception { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.setEnableDefaultSerializer(false); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate.afterPropertiesSet(); - - while (redisTemplate.boundListOps(TEST_QUEUE).rightPop() != null) { - // drain - } - - RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(TEST_QUEUE, - this.connectionFactory); - BoundListOperations boundListOperations = - TestUtils.getPropertyValue(endpoint, "boundListOperations", BoundListOperations.class); - boundListOperations = Mockito.spy(boundListOperations); - DirectFieldAccessor dfa = new DirectFieldAccessor(endpoint); - dfa.setPropertyValue("boundListOperations", boundListOperations); - endpoint.setBeanFactory(Mockito.mock(BeanFactory.class)); - endpoint.setOutputChannel(new DirectChannel()); - endpoint.setReceiveTimeout(10); - - ExecutorService executorService = Executors.newCachedThreadPool(); - endpoint.setTaskExecutor(executorService); - - endpoint.afterPropertiesSet(); - endpoint.start(); - - waitListening(endpoint); - dfa.setPropertyValue("listening", false); - - redisTemplate.boundListOps(TEST_QUEUE).leftPush("foo"); - - CountDownLatch stopLatch = new CountDownLatch(1); - - endpoint.stop(stopLatch::countDown); - - executorService.shutdown(); - assertThat(executorService.awaitTermination(20, TimeUnit.SECONDS)).isTrue(); - - assertThat(stopLatch.await(21, TimeUnit.SECONDS)).isTrue(); - - verify(boundListOperations, atLeastOnce()).rightPush(any(byte[].class)); - } - - @Test - @Disabled("LettuceConnectionFactory doesn't support proper reinitialization after 'destroy()'") - void testInt3196Recovery() throws Exception { - QueueChannel channel = new QueueChannel(); - - final List exceptionEvents = new ArrayList<>(); - - final CountDownLatch exceptionsLatch = new CountDownLatch(2); - - RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(TEST_QUEUE, - this.connectionFactory); - endpoint.setBeanFactory(Mockito.mock(BeanFactory.class)); - endpoint.setApplicationEventPublisher(event -> { - exceptionEvents.add((ApplicationEvent) event); - exceptionsLatch.countDown(); - }); - endpoint.setOutputChannel(channel); - endpoint.setReceiveTimeout(100); - endpoint.setRecoveryInterval(200); - endpoint.afterPropertiesSet(); - endpoint.start(); - - waitListening(endpoint); - - ((DisposableBean) this.connectionFactory).destroy(); - - assertThat(exceptionsLatch.await(10, TimeUnit.SECONDS)).isTrue(); - - for (ApplicationEvent exceptionEvent : exceptionEvents) { - assertThat(exceptionEvent).isInstanceOf(RedisExceptionEvent.class); - assertThat(exceptionEvent.getSource()).isSameAs(endpoint); - assertThat(((IntegrationEvent) exceptionEvent).getCause().getClass()) - .isIn(RedisSystemException.class, RedisConnectionFailureException.class); - } - - ((InitializingBean) this.connectionFactory).afterPropertiesSet(); - - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory); - redisTemplate.setEnableDefaultSerializer(false); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate.afterPropertiesSet(); - - String payload = "testing"; - - redisTemplate.boundListOps(TEST_QUEUE).leftPush(payload); - - Message receive = channel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload); - - CountDownLatch stopLatch = new CountDownLatch(1); - endpoint.stop(stopLatch::countDown); - assertThat(stopLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - void testInt3932ReadFromLeft() throws InterruptedException { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.setEnableDefaultSerializer(false); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate.afterPropertiesSet(); - - String payload = "testing"; - - redisTemplate.boundListOps(TEST_QUEUE).rightPush(payload); - - Date payload2 = new Date(); - - redisTemplate.boundListOps(TEST_QUEUE).rightPush(payload2); - - PollableChannel channel = new QueueChannel(); - - RedisQueueMessageDrivenEndpoint endpoint = - new RedisQueueMessageDrivenEndpoint(TEST_QUEUE, this.connectionFactory); - endpoint.setBeanFactory(Mockito.mock(BeanFactory.class)); - endpoint.setBeanClassLoader(ClassUtils.getDefaultClassLoader()); - endpoint.setOutputChannel(channel); - endpoint.setReceiveTimeout(10); - endpoint.setRightPop(false); - endpoint.afterPropertiesSet(); - endpoint.start(); - - Message receive = (Message) channel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload); - - receive = (Message) channel.receive(10000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(payload2); - - CountDownLatch stopLatch = new CountDownLatch(1); - endpoint.stop(stopLatch::countDown); - assertThat(stopLatch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - private void waitListening(RedisQueueMessageDrivenEndpoint endpoint) throws InterruptedException { - int n = 0; - do { - n++; - if (n == 100) { - break; - } - Thread.sleep(100); - } - while (!endpoint.isListening()); - - assertThat(n < 100).isTrue(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisStoreInboundChannelAdapterIntegrationTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisStoreInboundChannelAdapterIntegrationTests.java deleted file mode 100644 index 13c83a6d2e1..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisStoreInboundChannelAdapterIntegrationTests.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.inbound; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.support.collections.RedisZSet; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.messaging.Message; -import org.springframework.messaging.SubscribableChannel; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * @since 2.2 - */ -class RedisStoreInboundChannelAdapterIntegrationTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @Test - @SuppressWarnings("unchecked") - void testListInboundConfiguration() { - RedisContainerTest.prepareList(redisConnectionFactory); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass()); - SourcePollingChannelAdapter spca = context.getBean("listAdapter", SourcePollingChannelAdapter.class); - spca.start(); - QueueChannel redisChannel = context.getBean("redisChannel", QueueChannel.class); - - Message message = (Message) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo(Integer.valueOf(13)); - - //poll again, should get the same stuff - message = (Message) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo(Integer.valueOf(13)); - RedisContainerTest.deletePresidents(redisConnectionFactory); - context.close(); - } - - @Test - @SuppressWarnings("unchecked") - // synchronization commit renames the list - void testListInboundConfigurationWithSynchronization() throws Exception { - StringRedisTemplate template = RedisContainerTest.createStringRedisTemplate(redisConnectionFactory); - template.delete("bar"); - RedisContainerTest.prepareList(redisConnectionFactory); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass()); - SourcePollingChannelAdapter spca = - context.getBean("listAdapterWithSynchronization", SourcePollingChannelAdapter.class); - spca.start(); - QueueChannel redisChannel = context.getBean("redisChannel", QueueChannel.class); - - Message message = (Message) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo(Integer.valueOf(13)); - - //poll again, should get nothing since the collection was removed during synchronization - message = (Message) redisChannel.receive(100); - assertThat(message).isNull(); - - int n = 0; - while (n++ < 100 && template.keys("bar").size() == 0) { - Thread.sleep(100); - } - assertThat(n < 100).as("Rename didn't occur").isTrue(); - - assertThat(template.boundListOps("bar").size()).isEqualTo(Long.valueOf(13)); - template.delete("bar"); - - spca.stop(); - context.close(); - } - - @SuppressWarnings("resource") - @Test - // synchronization rollback renames the list - void testListInboundConfigurationWithSynchronizationAndRollback() throws Exception { - StringRedisTemplate template = RedisContainerTest.createStringRedisTemplate(redisConnectionFactory); - template.delete("baz"); - RedisContainerTest.prepareList(redisConnectionFactory); - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("list-inbound-adapter.xml", - this.getClass()); - SubscribableChannel fail = context.getBean("redisFailChannel", SubscribableChannel.class); - final CountDownLatch latch = new CountDownLatch(1); - fail.subscribe(message -> { - latch.countDown(); - throw new RuntimeException("Test Rollback"); - }); - SourcePollingChannelAdapter spca = context.getBean("listAdapterWithSynchronizationAndRollback", - SourcePollingChannelAdapter.class); - spca.start(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - int n = 0; - while (n++ < 100 && template.keys("baz").size() == 0) { - Thread.sleep(100); - } - assertThat(n < 100).as("Rename didn't occur").isTrue(); - assertThat(template.boundListOps("baz").size()).isEqualTo(Long.valueOf(13)); - template.delete("baz"); - - spca.stop(); - context.close(); - } - - @Test - @SuppressWarnings("unchecked") - // synchronization commit renames the list - void testListInboundConfigurationWithSynchronizationAndTemplate() throws Exception { - StringRedisTemplate template = RedisContainerTest.createStringRedisTemplate(redisConnectionFactory); - template.delete("bar"); - RedisContainerTest.prepareList(redisConnectionFactory); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass()); - SourcePollingChannelAdapter spca = - context.getBean("listAdapterWithSynchronizationAndRedisTemplate", SourcePollingChannelAdapter.class); - spca.start(); - QueueChannel redisChannel = context.getBean("redisChannel", QueueChannel.class); - - Message message = (Message) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo(Integer.valueOf(13)); - - //poll again, should get nothing since the collection was removed during synchronization - message = (Message) redisChannel.receive(100); - assertThat(message).isNull(); - - int n = 0; - while (n++ < 100 && template.keys("bar").size() == 0) { - Thread.sleep(100); - } - assertThat(n < 100).as("Rename didn't occur").isTrue(); - - assertThat(template.boundListOps("bar").size()).isEqualTo(Long.valueOf(13)); - template.delete("bar"); - - spca.stop(); - context.close(); - } - - @Test - @SuppressWarnings("unchecked") - void testZsetInboundAdapter() throws InterruptedException { - RedisContainerTest.prepareZset(redisConnectionFactory); - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("zset-inbound-adapter.xml", this.getClass()); - - //No Score test - SourcePollingChannelAdapter zsetAdapterNoScore = - context.getBean("zsetAdapterNoScore", SourcePollingChannelAdapter.class); - zsetAdapterNoScore.start(); - - QueueChannel redisChannel = context.getBean("redisChannel", QueueChannel.class); - - Message> message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).hasSize(13); - - //poll again, should get the same stuff - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).hasSize(13); - - zsetAdapterNoScore.stop(); - - //ScoreRange test - SourcePollingChannelAdapter zsetAdapterWithScoreRange = - context.getBean("zsetAdapterWithScoreRange", SourcePollingChannelAdapter.class); - zsetAdapterWithScoreRange.start(); - - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().rangeByScore(18, 20)).hasSize(11); - - //poll again, should get the same stuff - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().rangeByScore(18, 20)).hasSize(11); - - zsetAdapterWithScoreRange.stop(); - - //SingleScore test - SourcePollingChannelAdapter zsetAdapterWithSingleScore = - context.getBean("zsetAdapterWithSingleScore", SourcePollingChannelAdapter.class); - zsetAdapterWithSingleScore.start(); - - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().rangeByScore(18, 18)).hasSize(2); - - //poll again, should get the same stuff - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload().rangeByScore(18, 18)).hasSize(2); - - zsetAdapterWithSingleScore.stop(); - - //SingleScoreAndSynchronization test - SourcePollingChannelAdapter zsetAdapterWithSingleScoreAndSynchronization = - context.getBean("zsetAdapterWithSingleScoreAndSynchronization", SourcePollingChannelAdapter.class); - - QueueChannel otherRedisChannel = context.getBean("otherRedisChannel", QueueChannel.class); - - // get all 13 presidents - zsetAdapterNoScore.start(); - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - assertThat(message.getPayload()).hasSize(13); - zsetAdapterNoScore.stop(); - - // get only presidents for 18th century - zsetAdapterWithSingleScoreAndSynchronization.start(); - Message sizeMessage = (Message) otherRedisChannel.receive(10000); - assertThat(sizeMessage).isNotNull(); - assertThat(sizeMessage.getPayload()).isEqualTo(Integer.valueOf(2)); - - // ... however other elements are still available 13-2=11 - zsetAdapterNoScore.start(); - message = (Message>) redisChannel.receive(10000); - assertThat(message).isNotNull(); - - int n = 0; - while (n++ < 100 && message.getPayload().size() != 11) { - Thread.sleep(100); - } - assertThat(n < 100).isTrue(); - - zsetAdapterNoScore.stop(); - zsetAdapterWithSingleScoreAndSynchronization.stop(); - RedisContainerTest.deletePresidents(redisConnectionFactory); - - context.close(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/list-inbound-adapter.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/list-inbound-adapter.xml deleted file mode 100644 index 7a5dc8da85d..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/list-inbound-adapter.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/zset-inbound-adapter.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/zset-inbound-adapter.xml deleted file mode 100644 index 05efd02975b..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/zset-inbound-adapter.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/leader/RedisLockRegistryLeaderInitiatorTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/leader/RedisLockRegistryLeaderInitiatorTests.java deleted file mode 100644 index 09ec036f069..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/leader/RedisLockRegistryLeaderInitiatorTests.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.leader; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.integration.leader.Context; -import org.springframework.integration.leader.DefaultCandidate; -import org.springframework.integration.leader.event.LeaderEventPublisher; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.util.RedisLockRegistry; -import org.springframework.integration.support.leader.LockRegistryLeaderInitiator; -import org.springframework.integration.test.condition.LogLevels; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @author Gary Russell - * @author Glenn Renfro - * @author Artem Vozhdayenko - * - * @since 4.3.9 - */ -@LogLevels(categories = "org.springframework.integration.redis.leader") -class RedisLockRegistryLeaderInitiatorTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @Test - void testDistributedLeaderElection() throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, "LeaderInitiator"); - registry.expireUnusedOlderThan(-1); - CountDownLatch granted = new CountDownLatch(1); - CountingPublisher countingPublisher = new CountingPublisher(granted); - List initiators = new ArrayList<>(); - for (int i = 0; i < 2; i++) { - LockRegistryLeaderInitiator initiator = - new LockRegistryLeaderInitiator(registry, new DefaultCandidate("foo:" + i, "bar")); - initiator.setTaskExecutor(new SimpleAsyncTaskExecutor("lock-leadership-" + i + "-")); - initiator.setLeaderEventPublisher(countingPublisher); - initiators.add(initiator); - } - - for (LockRegistryLeaderInitiator initiator : initiators) { - initiator.start(); - } - - assertThat(granted.await(60, TimeUnit.SECONDS)).isTrue(); - - LockRegistryLeaderInitiator initiator1 = countingPublisher.initiator; - - LockRegistryLeaderInitiator initiator2 = null; - - for (LockRegistryLeaderInitiator initiator : initiators) { - if (initiator != initiator1) { - initiator2 = initiator; - break; - } - } - - assertThat(initiator2).isNotNull(); - - assertThat(initiator1.getContext().isLeader()).isTrue(); - assertThat(initiator2.getContext().isLeader()).isFalse(); - - final CountDownLatch granted1 = new CountDownLatch(1); - final CountDownLatch granted2 = new CountDownLatch(1); - CountDownLatch revoked1 = new CountDownLatch(1); - CountDownLatch revoked2 = new CountDownLatch(1); - CountDownLatch acquireLockFailed1 = new CountDownLatch(1); - CountDownLatch acquireLockFailed2 = new CountDownLatch(1); - - initiator1.setLeaderEventPublisher(new CountingPublisher(granted1, revoked1, acquireLockFailed1)); - - initiator2.setLeaderEventPublisher(new CountingPublisher(granted2, revoked2, acquireLockFailed2)); - - // It's hard to see round-robin election, so let's make the yielding initiator to sleep long before restarting - initiator1.setBusyWaitMillis(10000); - - Thread.sleep(100); - - initiator1.getContext().yield(); - - assertThat(revoked1.await(60, TimeUnit.SECONDS)).isTrue(); - assertThat(granted2.await(60, TimeUnit.SECONDS)).isTrue(); - - assertThat(initiator2.getContext().isLeader()).isTrue(); - assertThat(initiator1.getContext().isLeader()).isFalse(); - - initiator1.setBusyWaitMillis(LockRegistryLeaderInitiator.DEFAULT_BUSY_WAIT_TIME); - initiator2.setBusyWaitMillis(10000); - - Thread.sleep(100); - - initiator2.getContext().yield(); - - assertThat(revoked2.await(60, TimeUnit.SECONDS)).isTrue(); - assertThat(granted1.await(60, TimeUnit.SECONDS)).isTrue(); - - assertThat(initiator1.getContext().isLeader()).isTrue(); - assertThat(initiator2.getContext().isLeader()).isFalse(); - - initiator2.stop(); - - CountDownLatch revoked11 = new CountDownLatch(1); - initiator1.setLeaderEventPublisher(new CountingPublisher(new CountDownLatch(1), revoked11, - new CountDownLatch(1))); - - initiator1.stop(); - - assertThat(revoked11.await(60, TimeUnit.SECONDS)).isTrue(); - assertThat(initiator1.getContext().isLeader()).isFalse(); - } - - private static class CountingPublisher implements LeaderEventPublisher { - - private final CountDownLatch granted; - - private final CountDownLatch revoked; - - private volatile LockRegistryLeaderInitiator initiator; - - private final CountDownLatch acquireLockFailed; - - CountingPublisher(CountDownLatch granted, CountDownLatch revoked, CountDownLatch acquireLockFailed) { - this.granted = granted; - this.revoked = revoked; - this.acquireLockFailed = acquireLockFailed; - } - - CountingPublisher(CountDownLatch granted) { - this(granted, new CountDownLatch(1), new CountDownLatch(1)); - } - - @Override - public void publishOnRevoked(Object source, Context context, String role) { - this.revoked.countDown(); - } - - @Override - public void publishOnFailedToAcquire(Object source, Context context, String role) { - this.acquireLockFailed.countDown(); - } - - @Override - public void publishOnGranted(Object source, Context context, String role) { - this.initiator = (LockRegistryLeaderInitiator) source; - this.granted.countDown(); - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/metadata/RedisMetadataStoreTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/metadata/RedisMetadataStoreTests.java deleted file mode 100644 index ad415882f7f..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/metadata/RedisMetadataStoreTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.metadata; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundHashOperations; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.integration.redis.RedisContainerTest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Gunnar Hillert - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * @since 3.0 - * - */ -class RedisMetadataStoreTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @BeforeEach - @AfterEach - public void setUpTearDown() { - RedisContainerTest.createStringRedisTemplate(redisConnectionFactory).delete("testMetadata"); - } - - @Test - void testGetNonExistingKeyValue() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory); - String retrievedValue = metadataStore.get("does-not-exist"); - assertThat(retrievedValue).isNull(); - } - - @Test - void testPersistKeyValue() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - metadataStore.put("RedisMetadataStoreTests-Spring", "Integration"); - - StringRedisTemplate redisTemplate = new StringRedisTemplate(redisConnectionFactory); - BoundHashOperations ops = redisTemplate.boundHashOps("testMetadata"); - - assertThat(ops.get("RedisMetadataStoreTests-Spring")).isEqualTo("Integration"); - } - - @Test - void testGetValueFromMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - metadataStore.put("RedisMetadataStoreTests-GetValue", "Hello Redis"); - - String retrievedValue = metadataStore.get("RedisMetadataStoreTests-GetValue"); - assertThat(retrievedValue).isEqualTo("Hello Redis"); - } - - @Test - void testPersistEmptyStringToMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - metadataStore.put("RedisMetadataStoreTests-PersistEmpty", ""); - - String retrievedValue = metadataStore.get("RedisMetadataStoreTests-PersistEmpty"); - assertThat(retrievedValue).isEmpty(); - } - - @Test - void testPersistNullStringToMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - - try { - metadataStore.put("RedisMetadataStoreTests-PersistEmpty", null); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo("'value' must not be null."); - return; - } - - fail("Expected an IllegalArgumentException to be thrown."); - - } - - @Test - void testPersistWithEmptyKeyToMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - metadataStore.put("", "PersistWithEmptyKey"); - - String retrievedValue = metadataStore.get(""); - assertThat(retrievedValue).isEqualTo("PersistWithEmptyKey"); - } - - @Test - void testPersistWithNullKeyToMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - - try { - metadataStore.put(null, "something"); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo("'key' must not be null."); - return; - } - - fail("Expected an IllegalArgumentException to be thrown."); - } - - @Test - void testGetValueWithNullKeyFromMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - - try { - metadataStore.get(null); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo("'key' must not be null."); - return; - } - - fail("Expected an IllegalArgumentException to be thrown."); - } - - @Test - void testRemoveFromMetadataStore() { - RedisMetadataStore metadataStore = new RedisMetadataStore(redisConnectionFactory, "testMetadata"); - - String testKey = "RedisMetadataStoreTests-Remove"; - String testValue = "Integration"; - - metadataStore.put(testKey, testValue); - - assertThat(metadataStore.remove(testKey)).isEqualTo(testValue); - assertThat(metadataStore.remove(testKey)).isNull(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/ReactiveRedisStreamMessageHandlerTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/ReactiveRedisStreamMessageHandlerTests.java deleted file mode 100644 index 6fa3f4a571f..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/ReactiveRedisStreamMessageHandlerTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2020-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.connection.stream.ObjectRecord; -import org.springframework.data.redis.connection.stream.StreamOffset; -import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.util.Address; -import org.springframework.integration.redis.util.Person; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Attoumane Ahamadi - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 5.4 - */ -@SpringJUnitConfig -@DirtiesContext -class ReactiveRedisStreamMessageHandlerTests implements RedisContainerTest { - - private static final String STREAM_KEY = ReactiveRedisStreamMessageHandlerTests.class.getSimpleName() + ".stream"; - - @Autowired - @Qualifier("streamChannel") - private MessageChannel messageChannel; - - @Autowired - private ReactiveMessageHandlerAdapter handlerAdapter; - - @Autowired - private ReactiveRedisConnectionFactory redisConnectionFactory; - - @BeforeEach - void deleteStreamKey() { - ReactiveRedisTemplate template = - new ReactiveRedisTemplate<>(redisConnectionFactory, RedisSerializationContext.string()); - template.delete(STREAM_KEY).block(); - } - - @Test - void testIntegrationStreamOutbound() { - String messagePayload = "Hello stream message"; - - this.messageChannel.send(new GenericMessage<>(messagePayload)); - - ReactiveRedisTemplate template = - new ReactiveRedisTemplate<>(redisConnectionFactory, RedisSerializationContext.string()); - - ObjectRecord record = - template.opsForStream() - .read(String.class, StreamOffset.fromStart(STREAM_KEY)) - .blockFirst(); - - assertThat(record.getStream()).isEqualTo(STREAM_KEY); - - assertThat(record.getValue()).isEqualTo(messagePayload); - } - - @Test - void testMessageWithListPayload() { - List messagePayload = Arrays.asList("Hello", "stream", "message"); - this.handlerAdapter.handleMessage(new GenericMessage<>(messagePayload)); - - ReactiveRedisTemplate template = new ReactiveRedisTemplate<>(redisConnectionFactory, - RedisSerializationContext.string()); - ObjectRecord record = template.opsForStream().read(List.class, StreamOffset.fromStart(STREAM_KEY)) - .blockFirst(); - - assertThat(record.getStream()).isEqualTo(STREAM_KEY); - assertThat(record.getValue()).isEqualTo(messagePayload); - } - - @Test - void testExplicitSerializationContextWithModel() { - Address address = new Address("Rennes, France"); - Person person = new Person(address, "Attoumane"); - - Message message = new GenericMessage<>(person); - - this.handlerAdapter.handleMessage(message); - - ReactiveRedisTemplate template = - new ReactiveRedisTemplate<>(redisConnectionFactory, RedisSerializationContext.string()); - - ObjectRecord record = - template.opsForStream() - .read(Person.class, StreamOffset.fromStart(STREAM_KEY)) - .blockFirst(); - - assertThat(record.getStream()).isEqualTo(STREAM_KEY); - assertThat(record.getValue().getName()).isEqualTo("Attoumane"); - assertThat(record.getValue().getAddress().getAddress()).isEqualTo("Rennes, France"); - } - - @Configuration - @EnableIntegration - public static class ReactiveRedisStreamMessageHandlerTestsContext { - - @Bean - ReactiveRedisConnectionFactory redisConnectionFactory() { - return RedisContainerTest.connectionFactory(); - } - - @Bean - public ReactiveRedisStreamMessageHandler streamMessageHandler( - ReactiveRedisConnectionFactory redisConnectionFactory) { - - return new ReactiveRedisStreamMessageHandler(redisConnectionFactory, STREAM_KEY); - } - - @Bean - public ReactiveMessageHandlerAdapter reactiveMessageHandlerAdapter( - ReactiveRedisStreamMessageHandler streamMessageHandler) { - - return new ReactiveMessageHandlerAdapter(streamMessageHandler); - } - - @Bean - public MessageChannel streamChannel(ReactiveMessageHandlerAdapter messageHandlerAdapter) { - DirectChannel directChannel = new DirectChannel(); - directChannel.subscribe(messageHandlerAdapter); - directChannel.setMaxSubscribers(1); - return directChannel; - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisOutboundGatewayTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisOutboundGatewayTests-context.xml deleted file mode 100644 index f5285e93160..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisOutboundGatewayTests-context.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisOutboundGatewayTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisOutboundGatewayTests.java deleted file mode 100644 index 53df9a4fdc7..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisOutboundGatewayTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.integration.handler.ReplyRequiredException; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 4.0 - */ -@SpringJUnitConfig -@DirtiesContext -class RedisOutboundGatewayTests implements RedisContainerTest { - - @Autowired - private BeanFactory beanFactory; - - @Autowired - private PollableChannel replyChannel; - - @Autowired - private MessageChannel pingChannel; - - @Autowired - private MessageChannel leftPushRightPopChannel; - - @Autowired - private MessageChannel incrementAtomicIntegerChannel; - - @Autowired - private MessageChannel setDelCommandChannel; - - @Autowired - private MessageChannel getCommandChannel; - - @Autowired - private MessageChannel mgetCommandChannel; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - @Test - void testPingPongCommand() { - this.pingChannel.send(MessageBuilder.withPayload("foo").setHeader(RedisHeaders.COMMAND, "PING").build()); - Message receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(Arrays.equals("PONG".getBytes(), (byte[]) receive.getPayload())).isTrue(); - } - - @Test - void testPushAndPopCommands() { - final String queueName = "si.test.testRedisOutboundGateway"; - String payload = "testing"; - this.leftPushRightPopChannel.send(MessageBuilder.withPayload(payload) - .setHeader(RedisHeaders.COMMAND, "LPUSH") - .setHeader("queue", queueName) - .build()); - Message receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - - this.leftPushRightPopChannel.send(MessageBuilder.withPayload(payload) - .setHeader(RedisHeaders.COMMAND, "RPOP") - .setHeader("queue", queueName) - .build()); - receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(Arrays.equals(payload.getBytes(), (byte[]) receive.getPayload())).isTrue(); - } - - @Test - void testIncrementAtomicCommand() { - // Since 'atomicInteger' is lazy-init to avoid early Redis connection, - // we have to initialize it before send the INCR command. - this.beanFactory.getBean("atomicInteger"); - this.incrementAtomicIntegerChannel.send(MessageBuilder.withPayload("INCR").build()); - Message receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(11L); - - this.getCommandChannel.send(MessageBuilder.withPayload("si.test.RedisAtomicInteger").build()); - receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(new String((byte[]) receive.getPayload())).isEqualTo("11"); - RedisContainerTest.createStringRedisTemplate(redisConnectionFactory).delete("si.test.RedisAtomicInteger"); - } - - @Test - void testGetCommand() { - this.setDelCommandChannel.send(MessageBuilder.withPayload(new String[] {"foo", "bar"}) - .setHeader(RedisHeaders.COMMAND, "SET").build()); - Message receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo("OK"); - - this.getCommandChannel.send(MessageBuilder.withPayload("foo").build()); - receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(Arrays.equals("bar".getBytes(), (byte[]) receive.getPayload())).isTrue(); - - this.setDelCommandChannel.send(MessageBuilder.withPayload("foo").setHeader(RedisHeaders.COMMAND, "DEL").build()); - receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isEqualTo(1L); - - try { - this.getCommandChannel.send(MessageBuilder.withPayload("foo").build()); - fail("ReplyRequiredException expected"); - } - catch (Exception e) { - assertThat(e).isInstanceOf(ReplyRequiredException.class); - } - } - - @SuppressWarnings("unchecked") - @Test - void testMGetCommand() { - RedisConnection connection = redisConnectionFactory.getConnection(); - byte[] value1 = "bar1".getBytes(); - byte[] value2 = "bar2".getBytes(); - connection.stringCommands().set("foo1".getBytes(), value1); - connection.stringCommands().set("foo2".getBytes(), value2); - this.mgetCommandChannel.send(MessageBuilder.withPayload(new String[] {"foo1", "foo2"}).build()); - Message receive = this.replyChannel.receive(1000); - assertThat(receive).isNotNull(); - assertThat((List) receive.getPayload()).containsExactly(value1, value2); - connection.keyCommands().del("foo1".getBytes(), "foo2".getBytes()); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java deleted file mode 100644 index f9a8e99b194..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.listener.Topic; -import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.support.MessageBuilder; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Artem Bilan - * @author Artem Vozhdayenko - * @since 2.1 - */ -class RedisPublishingMessageHandlerTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @Test - void testRedisPublishingMessageHandler() throws Exception { - int numToTest = 10; - String topic = "si.test.channel"; - final CountDownLatch latch = new CountDownLatch(numToTest * 2); - - MessageListenerAdapter listener = new MessageListenerAdapter(); - listener.setDelegate(new Listener(latch)); - listener.setSerializer(new StringRedisSerializer()); - listener.afterPropertiesSet(); - - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(redisConnectionFactory); - container.afterPropertiesSet(); - container.addMessageListener(listener, Collections.singletonList(new ChannelTopic(topic))); - container.start(); - - RedisContainerTest.awaitContainerSubscribed(container); - - final RedisPublishingMessageHandler handler = new RedisPublishingMessageHandler(redisConnectionFactory); - handler.setTopicExpression(new LiteralExpression(topic)); - - for (int i = 0; i < numToTest; i++) { - handler.handleMessage(MessageBuilder.withPayload("test-" + i).build()); - } - - for (int i = 0; i < numToTest; i++) { - handler.handleMessage(MessageBuilder.withPayload(("test-" + i).getBytes()).build()); - } - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - container.stop(); - } - - private static class Listener { - - private final CountDownLatch latch; - - Listener(CountDownLatch latch) { - this.latch = latch; - } - - @SuppressWarnings("unused") - public void handleMessage(String s) { - this.latch.countDown(); - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests-context.xml deleted file mode 100644 index 51bca16a004..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests-context.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java deleted file mode 100644 index 737681c314a..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.Arrays; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.Jackson3JsonRedisSerializer; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.mapping.InboundMessageMapper; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.json.JacksonJsonMessageParser; -import org.springframework.integration.support.json.JsonInboundMessageMapper; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gunnar Hillert - * @author Artem Bilan - * @author Rainer Frey - * @author Artem Vozhdayenko - * - * @since 3.0 - */ -@SpringJUnitConfig -@DirtiesContext -class RedisQueueOutboundChannelAdapterTests implements RedisContainerTest { - - @Autowired - private RedisConnectionFactory connectionFactory; - - @Autowired - @Qualifier("toRedisQueueChannel") - private MessageChannel sendChannel; - - @Test - void testInt3015Default() { - - final String queueName = "si.test.testRedisQueueOutboundChannelAdapter"; - - final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, - this.connectionFactory); - - String payload = "testing"; - handler.handleMessage(MessageBuilder.withPayload(payload).build()); - - RedisTemplate redisTemplate = new StringRedisTemplate(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.afterPropertiesSet(); - - Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS); - assertThat(result) - .isNotNull() - .isEqualTo(payload); - - Date payload2 = new Date(); - handler.handleMessage(MessageBuilder.withPayload(payload2).build()); - - RedisTemplate redisTemplate2 = new RedisTemplate(); - redisTemplate2.setConnectionFactory(this.connectionFactory); - redisTemplate2.setEnableDefaultSerializer(false); - redisTemplate2.setKeySerializer(new StringRedisSerializer()); - redisTemplate2.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate2.afterPropertiesSet(); - - Object result2 = redisTemplate2.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS); - assertThat(result2) - .isNotNull() - .isEqualTo(payload2); - } - - @Test - void testInt3015ExtractPayloadFalse() { - - final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2"; - - final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, - this.connectionFactory); - handler.setExtractPayload(false); - - Message message = MessageBuilder.withPayload("testing").build(); - handler.handleMessage(message); - - RedisTemplate redisTemplate = new RedisTemplate(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.setEnableDefaultSerializer(false); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate.afterPropertiesSet(); - - Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS); - assertThat(result) - .isNotNull() - .isEqualTo(message); - - } - - @Test - void testInt3015ExplicitSerializer() { - - final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2"; - - final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, - this.connectionFactory); - handler.setSerializer(new Jackson3JsonRedisSerializer(Object.class)); - - RedisTemplate redisTemplate = new StringRedisTemplate(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.afterPropertiesSet(); - - handler.handleMessage(new GenericMessage(Arrays.asList("foo", "bar", "baz"))); - - Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS); - assertThat(result) - .isNotNull() - .isEqualTo("[\"foo\",\"bar\",\"baz\"]"); - - handler.handleMessage(new GenericMessage("test")); - - result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS); - assertThat(result) - .isNotNull() - .isEqualTo("\"test\""); - } - - @Test - void testInt3017IntegrationOutbound() { - - final String queueName = "si.test.Int3017IntegrationOutbound"; - - GenericMessage message = new GenericMessage(queueName); - this.sendChannel.send(message); - - RedisTemplate redisTemplate = new StringRedisTemplate(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.afterPropertiesSet(); - - String result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS); - assertThat(result).isNotNull(); - InboundMessageMapper mapper = new JsonInboundMessageMapper(String.class, - new JacksonJsonMessageParser()); - Message resultMessage = mapper.toMessage(result); - assertThat(resultMessage.getPayload()).isEqualTo(message.getPayload()); - } - - @Test - void testInt3932LeftPushFalse() { - - final String queueName = "si.test.Int3932LeftPushFalse"; - - final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, - this.connectionFactory); - handler.setLeftPush(false); - - String payload = "testing"; - handler.handleMessage(MessageBuilder.withPayload(payload).build()); - - Date payload2 = new Date(); - handler.handleMessage(MessageBuilder.withPayload(payload2).build()); - - RedisTemplate redisTemplate = new StringRedisTemplate(); - redisTemplate.setConnectionFactory(this.connectionFactory); - redisTemplate.afterPropertiesSet(); - - Object result = redisTemplate.boundListOps(queueName).leftPop(5000, TimeUnit.MILLISECONDS); - assertThat(result) - .isNotNull() - .isEqualTo(payload); - - RedisTemplate redisTemplate2 = new RedisTemplate(); - redisTemplate2.setConnectionFactory(this.connectionFactory); - redisTemplate2.setEnableDefaultSerializer(false); - redisTemplate2.setKeySerializer(new StringRedisSerializer()); - redisTemplate2.setValueSerializer(new JdkSerializationRedisSerializer()); - redisTemplate2.afterPropertiesSet(); - - Object result2 = redisTemplate2.boundListOps(queueName).leftPop(5000, TimeUnit.MILLISECONDS); - assertThat(result2) - .isNotNull() - .isEqualTo(payload2); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreOutboundChannelAdapterIntegrationTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreOutboundChannelAdapterIntegrationTests-context.xml deleted file mode 100644 index a3f47830484..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreOutboundChannelAdapterIntegrationTests-context.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreOutboundChannelAdapterIntegrationTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreOutboundChannelAdapterIntegrationTests.java deleted file mode 100644 index 2cbcffe3da7..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreOutboundChannelAdapterIntegrationTests.java +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.data.redis.support.collections.DefaultRedisList; -import org.springframework.data.redis.support.collections.DefaultRedisMap; -import org.springframework.data.redis.support.collections.DefaultRedisSet; -import org.springframework.data.redis.support.collections.DefaultRedisZSet; -import org.springframework.data.redis.support.collections.RedisList; -import org.springframework.data.redis.support.collections.RedisMap; -import org.springframework.data.redis.support.collections.RedisProperties; -import org.springframework.data.redis.support.collections.RedisSet; -import org.springframework.data.redis.support.collections.RedisZSet; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 2.2 - */ -@SpringJUnitConfig -@DirtiesContext -class RedisStoreOutboundChannelAdapterIntegrationTests implements RedisContainerTest { - - private final StringRedisTemplate redisTemplate = new StringRedisTemplate(); - - @Autowired - private BeanFactory beanFactory; - - @Autowired - @Qualifier("listWithKeyAsHeader") - private MessageChannel listWithKeyAsHeaderChannel; - - @Autowired - @Qualifier("listWithKeyProvided") - private MessageChannel listWithKeyProvidedChannel; - - @Autowired - @Qualifier("zset") - private MessageChannel zsetChannel; - - @Autowired - @Qualifier("mapToZset") - private MessageChannel mapToZsetChannel; - - @Autowired - @Qualifier("mapToMapA") - private MessageChannel mapToMapAChannel; - - @Autowired - @Qualifier("mapToMapB") - private MessageChannel mapToMapBChannel; - - @Autowired - @Qualifier("simpleMap") - private MessageChannel simpleMapChannel; - - @Autowired - @Qualifier("set") - private MessageChannel setChannel; - - @Autowired - @Qualifier("setNotParsed") - private MessageChannel setNotParsedChannel; - - @Autowired - @Qualifier("pojoIntoSet") - private MessageChannel pojoIntoSetChannel; - - @Autowired - @Qualifier("property") - private MessageChannel propertyChannel; - - @Autowired - @Qualifier("simpleProperty") - private MessageChannel simplePropertyChannel; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - @BeforeEach - @AfterEach - public void setup() { - this.redisTemplate.setConnectionFactory(redisConnectionFactory); - this.redisTemplate.afterPropertiesSet(); - - this.redisTemplate.delete("pepboys"); - this.redisTemplate.delete("foo"); - this.redisTemplate.delete("bar"); - this.redisTemplate.delete("presidents"); - } - - @Test - void testListWithKeyAsHeader() { - RedisList redisList = new DefaultRedisList("pepboys", this.redisTemplate); - assertThat(redisList).isEmpty(); - - List pepboys = new ArrayList(); - pepboys.add("Manny"); - pepboys.add("Moe"); - pepboys.add("Jack"); - Message> message = MessageBuilder.withPayload(pepboys).setHeader(RedisHeaders.KEY, "pepboys").build(); - this.listWithKeyAsHeaderChannel.send(message); - - assertThat(redisList).hasSize(3); - } - - @Test - void testListWithKeyAsHeaderSimple() { - redisTemplate.delete("foo"); - RedisList redisList = new DefaultRedisList("foo", this.redisTemplate); - assertThat(redisList).isEmpty(); - - Message message = MessageBuilder.withPayload("bar").setHeader("redis_key", "foo").build(); - this.listWithKeyAsHeaderChannel.send(message); - - assertThat(redisList).hasSize(1); - } - - @Test - void testListWithProvidedKey() { - RedisList redisList = new DefaultRedisList("pepboys", this.redisTemplate); - assertThat(redisList).isEmpty(); - - List pepboys = new ArrayList(); - pepboys.add("Manny"); - pepboys.add("Moe"); - pepboys.add("Jack"); - Message> message = MessageBuilder.withPayload(pepboys).build(); - this.listWithKeyProvidedChannel.send(message); - - assertThat(redisList).hasSize(3); - } - - @Test - void testZsetSimplePayloadIncrement() { - RedisZSet redisZSet = new DefaultRedisZSet("foo", this.redisTemplate); - assertThat(redisZSet).isEmpty(); - - Message message = MessageBuilder.withPayload("bar") - .setHeader(RedisHeaders.KEY, "foo") - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, true) - .build(); - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(1)); - - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(2)); - } - - @Test - void testZsetSimplePayloadOverwrite() { - RedisZSet redisZSet = new DefaultRedisZSet("foo", this.redisTemplate); - assertThat(redisZSet).isEmpty(); - - Message message = MessageBuilder.withPayload("bar") - .setHeader(RedisHeaders.KEY, "foo") - .build(); - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(1)); - - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(1)); - } - - @Test - void testZsetSimplePayloadIncrementBy2() { - RedisZSet redisZSet = new DefaultRedisZSet("foo", this.redisTemplate); - assertThat(redisZSet).isEmpty(); - - Message message = MessageBuilder.withPayload("bar") - .setHeader(RedisHeaders.KEY, "foo") - .setHeader(RedisHeaders.ZSET_SCORE, 2) - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, true) - .build(); - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(2)); - - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(4)); - } - - @Test - void testZsetSimplePayloadOverwriteWithHeaderScore() { - RedisZSet redisZSet = new DefaultRedisZSet("foo", this.redisTemplate); - assertThat(redisZSet).isEmpty(); - - Message message = MessageBuilder.withPayload("bar") - .setHeader(RedisHeaders.KEY, "foo") - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, false) - .setHeader(RedisHeaders.ZSET_SCORE, 2) - .build(); - this.zsetChannel.send(message); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(2)); - - this.zsetChannel.send(MessageBuilder.fromMessage(message).setHeader(RedisHeaders.ZSET_SCORE, 15).build()); - - assertThat(redisZSet).hasSize(1); - assertThat(redisZSet.score("bar")).isEqualTo(Double.valueOf(15)); - } - - @Test - void testMapToZsetWithProvidedKey() { - RedisZSet redisZset = new DefaultRedisZSet("presidents", this.redisTemplate); - assertThat(redisZset).isEmpty(); - - Map presidents = new HashMap(); - presidents.put("John Adams", 18); - - presidents.put("Barack Obama", 21); - presidents.put("Thomas Jefferson", 19); - presidents.put("John Quincy Adams", 19); - presidents.put("Zachary Taylor", 19); - - Message> message = MessageBuilder.withPayload(presidents) - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, true) - .build(); - - this.mapToZsetChannel.send(message); - - assertThat(redisZset).hasSize(5); - assertThat(redisZset.rangeByScore(18, 18)).hasSize(1); - assertThat(redisZset.rangeByScore(18, 19)).hasSize(4); - assertThat(redisZset.rangeByScore(21, 21)).hasSize(1); - - RedisStoreWritingMessageHandler handler = - this.beanFactory.getBean("mapToZset.handler", RedisStoreWritingMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "keyExpression.expression")).isEqualTo("'presidents'"); - - this.mapToZsetChannel.send(message); - - assertThat(redisZset).hasSize(5); - assertThat(redisZset.rangeByScore(36, 36)).hasSize(1); - assertThat(redisZset.rangeByScore(36, 38)).hasSize(4); - assertThat(redisZset.rangeByScore(42, 42)).hasSize(1); - - // test overwrite score behavior - presidents.put("Barack Obama", 31); - - this.mapToZsetChannel.send(MessageBuilder.fromMessage(message) - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, false) - .build()); - - assertThat(redisZset).hasSize(5); - assertThat(redisZset.rangeByScore(18, 18)).hasSize(1); - assertThat(redisZset.rangeByScore(18, 19)).hasSize(4); - assertThat(redisZset.rangeByScore(31, 31)).hasSize(1); - } - - @Test - void testMapToMapWithProvidedKey() { - RedisMap redisMap = new DefaultRedisMap("pepboys", this.redisTemplate); - - assertThat(redisMap).isEmpty(); - - Map pepboys = new HashMap(); - pepboys.put("1", "Manny"); - pepboys.put("2", "Moe"); - pepboys.put("3", "Jack"); - - Message> message = MessageBuilder.withPayload(pepboys).build(); - this.mapToMapAChannel.send(message); - assertThat(redisMap) - .containsEntry("1", "Manny") - .containsEntry("2", "Moe") - .containsEntry("3", "Jack"); - - RedisStoreWritingMessageHandler handler = this.beanFactory.getBean("mapToMapA.handler", - RedisStoreWritingMessageHandler.class); - assertThat(TestUtils.getPropertyValue(handler, "keyExpression", LiteralExpression.class).getExpressionString()) - .isEqualTo("pepboys"); - assertThat(TestUtils.getPropertyValue(handler, "mapKeyExpression", SpelExpression.class).getExpressionString()) - .isEqualTo("'foo'"); - } - - @Test - // map key is not provided - void testMapToMapAsSingleEntryWithKeyAsHeaderFail() { - RedisMap> redisMap = - new DefaultRedisMap>("pepboys", this.redisTemplate); - - assertThat(redisMap).isEmpty(); - - Map pepboys = new HashMap(); - pepboys.put("1", "Manny"); - pepboys.put("2", "Moe"); - pepboys.put("3", "Jack"); - - Message> message = MessageBuilder.withPayload(pepboys). - setHeader(RedisHeaders.KEY, "pepboys").build(); - - assertThatThrownBy(() -> this.mapToMapBChannel.send(message)) - .isInstanceOf(MessageHandlingException.class); - } - - @Test - // key is not provided - void testMapToMapNoKey() { - RedisTemplate>> redisTemplate = new RedisTemplate>>(); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setConnectionFactory(redisConnectionFactory); - redisTemplate.afterPropertiesSet(); - - RedisMap> redisMap = - new DefaultRedisMap>("pepboys", redisTemplate); - - assertThat(redisMap).isEmpty(); - - Map pepboys = new HashMap(); - pepboys.put("1", "Manny"); - pepboys.put("2", "Moe"); - pepboys.put("3", "Jack"); - - Message> message = MessageBuilder.withPayload(pepboys).build(); - assertThatThrownBy(() -> this.mapToMapBChannel.send(message)) - .isInstanceOf(MessageHandlingException.class); - } - - @Test - void testMapToMapAsSingleEntryWithKeyAsHeader() { - RedisTemplate>> redisTemplate = new RedisTemplate>>(); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setConnectionFactory(redisConnectionFactory); - redisTemplate.afterPropertiesSet(); - - RedisMap> redisMap = - new DefaultRedisMap>("pepboys", redisTemplate); - - assertThat(redisMap).isEmpty(); - - Map pepboys = new HashMap(); - pepboys.put("1", "Manny"); - pepboys.put("2", "Moe"); - pepboys.put("3", "Jack"); - - Message> message = MessageBuilder.withPayload(pepboys). - setHeader(RedisHeaders.KEY, "pepboys").setHeader(RedisHeaders.MAP_KEY, "foo").build(); - this.mapToMapBChannel.send(message); - Map pepboyz = redisMap.get("foo"); - - assertThat(pepboyz) - .containsEntry("1", "Manny") - .containsEntry("2", "Moe") - .containsEntry("3", "Jack"); - } - - @Test - void testStoreSimpleStringInMap() { - RedisMap redisMap = new DefaultRedisMap("bar", this.redisTemplate); - - assertThat(redisMap).isEmpty(); - - Message message = MessageBuilder.withPayload("hello, world!"). - setHeader(RedisHeaders.KEY, "bar").setHeader(RedisHeaders.MAP_KEY, "foo").build(); - - this.simpleMapChannel.send(message); - String hello = redisMap.get("foo"); - - assertThat(hello).isEqualTo("hello, world!"); - } - - @Test - void testSetWithKeyAsHeader() { - RedisSet redisSet = new DefaultRedisSet("pepboys", this.redisTemplate); - assertThat(redisSet).isEmpty(); - - Set pepboys = new HashSet(); - pepboys.add("Manny"); - pepboys.add("Moe"); - pepboys.add("Jack"); - Message> message = MessageBuilder.withPayload(pepboys).setHeader("redis_key", "pepboys").build(); - this.setChannel.send(message); - - assertThat(redisSet).hasSize(3); - } - - @Test - void testSetWithKeyAsHeaderSimple() { - RedisSet redisSet = new DefaultRedisSet("foo", this.redisTemplate); - assertThat(redisSet).isEmpty(); - - Message message = MessageBuilder.withPayload("foo") - .setHeader(RedisHeaders.KEY, "foo").build(); - this.setChannel.send(message); - - assertThat(redisSet).hasSize(1); - } - - @Test - void testSetWithKeyAsHeaderNotParsed() { - RedisSet redisSet = new DefaultRedisSet("pepboys", this.redisTemplate); - assertThat(redisSet).isEmpty(); - - Set pepboys = new HashSet(); - pepboys.add("Manny"); - pepboys.add("Moe"); - pepboys.add("Jack"); - Message> message = MessageBuilder.withPayload(pepboys).setHeader("redis_key", "pepboys").build(); - this.setNotParsedChannel.send(message); - - assertThat(redisSet).hasSize(1); - } - - @Test - void testPojoIntoSet() { - RedisSet redisSet = new DefaultRedisSet("pepboys", this.redisTemplate); - assertThat(redisSet).isEmpty(); - - String pepboy = "Manny"; - Message message = MessageBuilder.withPayload(pepboy).setHeader("redis_key", "pepboys").build(); - this.pojoIntoSetChannel.send(message); - - assertThat(redisSet).hasSize(1); - } - - @Test - void testProperties() { - RedisProperties redisProperties = new RedisProperties("pepboys", this.redisTemplate); - - assertThat(redisProperties).isEmpty(); - - Properties pepboys = new Properties(); - pepboys.put("1", "Manny"); - pepboys.put("2", "Moe"); - pepboys.put("3", "Jack"); - - Message message = MessageBuilder.withPayload(pepboys).build(); - this.propertyChannel.send(message); - - assertThat(redisProperties) - .containsEntry("1", "Manny") - .containsEntry("2", "Moe") - .containsEntry("3", "Jack"); - } - - @Test - void testPropertiesSimple() { - RedisProperties redisProperties = new RedisProperties("foo", this.redisTemplate); - - assertThat(redisProperties).isEmpty(); - - Message message = MessageBuilder.withPayload("bar") - .setHeader(RedisHeaders.KEY, "foo") - .setHeader("baz", "qux") - .build(); - this.simplePropertyChannel.send(message); - - assertThat(redisProperties).containsEntry("qux", "bar"); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreWritingMessageHandlerTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreWritingMessageHandlerTests.java deleted file mode 100644 index 86d5c88d6a7..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisStoreWritingMessageHandlerTests.java +++ /dev/null @@ -1,561 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.outbound; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.core.ZSetOperations.TypedTuple; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.data.redis.support.collections.DefaultRedisList; -import org.springframework.data.redis.support.collections.DefaultRedisZSet; -import org.springframework.data.redis.support.collections.RedisCollectionFactoryBean.CollectionType; -import org.springframework.data.redis.support.collections.RedisList; -import org.springframework.data.redis.support.collections.RedisZSet; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.support.RedisHeaders; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.support.TestApplicationContextAware; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Oleg Zhurakousky - * @author Gunnar Hillert - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - */ -class RedisStoreWritingMessageHandlerTests implements RedisContainerTest, TestApplicationContextAware { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @Test - void testListWithListPayloadParsedAndProvidedKey() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisList redisList = - new DefaultRedisList<>(key, this.initTemplate(redisConnectionFactory, new StringRedisTemplate())); - - assertThat(redisList).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = new GenericMessage<>(list); - handler.handleMessage(message); - - assertThat(redisList).hasSize(3); - assertThat(redisList.get(0)).isEqualTo("Manny"); - assertThat(redisList.get(1)).isEqualTo("Moe"); - assertThat(redisList.get(2)).isEqualTo("Jack"); - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testListWithListPayloadParsedAndProvidedKeyAsHeader() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisList redisList = - new DefaultRedisList<>(key, this.initTemplate(redisConnectionFactory, new StringRedisTemplate())); - - assertThat(redisList).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = MessageBuilder.withPayload(list).setHeader("redis_key", key).build(); - handler.handleMessage(message); - - assertThat(redisList).hasSize(3); - assertThat(redisList.get(0)).isEqualTo("Manny"); - assertThat(redisList.get(1)).isEqualTo("Moe"); - assertThat(redisList.get(2)).isEqualTo("Jack"); - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testListWithListPayloadParsedAndNoKey() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisList redisList = - new DefaultRedisList<>(key, this.initTemplate(redisConnectionFactory, new RedisTemplate<>())); - - assertThat(redisList).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = MessageBuilder.withPayload(list).build(); - assertThatThrownBy(() -> handler.handleMessage(message)).isInstanceOf(MessageHandlingException.class); - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testListWithListPayloadAsSingleEntry() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisList> redisList = - new DefaultRedisList<>(key, this.initTemplate(redisConnectionFactory, new RedisTemplate<>())); - - assertThat(redisList).isEmpty(); - - RedisTemplate> template = this.initTemplate(redisConnectionFactory, new RedisTemplate<>()); - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(template); - handler.setKey(key); - handler.setExtractPayloadElements(false); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = new GenericMessage<>(list); - handler.handleMessage(message); - - assertThat(redisList).hasSize(1); - List resultList = redisList.get(0); - assertThat(resultList.get(0)).isEqualTo("Manny"); - assertThat(resultList.get(1)).isEqualTo("Moe"); - assertThat(resultList.get(2)).isEqualTo("Jack"); - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testZsetWithListPayloadParsedAndProvidedKeyDefault() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisZSet redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new StringRedisTemplate())); - - assertThat(redisZset).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = MessageBuilder.withPayload(list) - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, true) - .build(); - handler.handleMessage(message); - - assertThat(redisZset).hasSize(3); - Set> pepboys = redisZset.rangeByScoreWithScores(1, 1); - for (TypedTuple pepboy : pepboys) { - assertThat(pepboy.getScore()).isEqualTo(1); - } - - handler.handleMessage(message); - assertThat(redisZset).hasSize(3); - pepboys = redisZset.rangeByScoreWithScores(1, 2); - // should have incremented by 1 - for (TypedTuple pepboy : pepboys) { - assertThat(pepboy.getScore()).isEqualTo(Double.valueOf(2)); - } - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testZsetWithListPayloadParsedAndProvidedKeyScoreIncrement() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisZSet redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new StringRedisTemplate())); - - assertThat(redisZset).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = MessageBuilder.withPayload(list) - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, Boolean.TRUE) - .build(); - - handler.handleMessage(message); - - assertThat(redisZset).hasSize(3); - Set> pepboys = redisZset.rangeByScoreWithScores(1, 1); - for (TypedTuple pepboy : pepboys) { - assertThat(pepboy.getScore()).isEqualTo(1); - } - - handler.handleMessage(message); - assertThat(redisZset).hasSize(3); - pepboys = redisZset.rangeByScoreWithScores(1, 2); - // should have incremented - for (TypedTuple pepboy : pepboys) { - assertThat(pepboy.getScore()).isEqualTo(2); - } - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testZsetWithListPayloadParsedAndProvidedKeyScoreIncrementAsStringHeader() { // see INT-2775 - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisZSet redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new StringRedisTemplate())); - - assertThat(redisZset).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = MessageBuilder.withPayload(list) - .setHeader(RedisHeaders.ZSET_INCREMENT_SCORE, "true") - .build(); - - handler.handleMessage(message); - - assertThat(redisZset).hasSize(3); - Set> pepboys = redisZset.rangeByScoreWithScores(1, 1); - for (TypedTuple pepboy : pepboys) { - assertThat(pepboy.getScore()).isEqualTo(1); - } - - handler.handleMessage(message); - assertThat(redisZset).hasSize(3); - pepboys = redisZset.rangeByScoreWithScores(1, 2); - // should have incremented - for (TypedTuple pepboy : pepboys) { - assertThat(pepboy.getScore()).isEqualTo(2); - } - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testZsetWithListPayloadAsSingleEntryAndHeaderKeyHeaderScore() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisZSet> redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new RedisTemplate<>())); - - assertThat(redisZset).isEmpty(); - - RedisTemplate> template = this.initTemplate(redisConnectionFactory, new RedisTemplate<>()); - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(template); - - handler.setCollectionType(CollectionType.ZSET); - handler.setExtractPayloadElements(false); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - List list = new ArrayList<>(); - list.add("Manny"); - list.add("Moe"); - list.add("Jack"); - Message> message = MessageBuilder.withPayload(list).setHeader("redis_key", key). - setHeader("redis_zsetScore", 4).build(); - handler.handleMessage(message); - - assertThat(redisZset).hasSize(1); - Set>> entries = redisZset.rangeByScoreWithScores(1, 4); - for (TypedTuple> pepboys : entries) { - assertThat(pepboys.getScore()).isEqualTo(4); - } - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testZsetWithMapPayloadParsedHeaderKey() { - RedisContainerTest.deletePresidents(redisConnectionFactory); - String key = "presidents"; - RedisZSet redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new StringRedisTemplate())); - - assertThat(redisZset).isEmpty(); - - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - Map presidents = new HashMap<>(); - presidents.put("John Adams", 18D); - - presidents.put("Barack Obama", 21D); - presidents.put("Thomas Jefferson", 19D); - presidents.put("John Quincy Adams", 19D); - presidents.put("Zachary Taylor", 19D); - - presidents.put("Theodore Roosevelt", 20D); - presidents.put("Woodrow Wilson", 20D); - presidents.put("George W. Bush", 21D); - presidents.put("Franklin D. Roosevelt", 20D); - presidents.put("Ronald Reagan", 20D); - presidents.put("William J. Clinton", 20D); - presidents.put("Abraham Lincoln", 19D); - presidents.put("George Washington", 18D); - - Message> message = MessageBuilder.withPayload(presidents).setHeader("redis_key", key).build(); - handler.handleMessage(message); - - assertThat(redisZset).hasSize(13); - - Set> entries = redisZset.rangeByScoreWithScores(18, 19); - assertThat(entries).hasSize(6); - RedisContainerTest.deletePresidents(redisConnectionFactory); - } - - @Test - void testZsetWithMapPayloadPojoParsedHeaderKey() { - RedisContainerTest.deletePresidents(redisConnectionFactory); - String key = "presidents"; - RedisZSet redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new RedisTemplate<>())); - - assertThat(redisZset).isEmpty(); - - RedisTemplate template = this.initTemplate(redisConnectionFactory, new RedisTemplate<>()); - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(template); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - Map presidents = new HashMap<>(); - presidents.put(new President("John Adams"), 18D); - - presidents.put(new President("Barack Obama"), 21D); - presidents.put(new President("Thomas Jefferson"), 19D); - presidents.put(new President("John Quincy Adams"), 19D); - presidents.put(new President("Zachary Taylor"), 19D); - - presidents.put(new President("Theodore Roosevelt"), 20D); - presidents.put(new President("Woodrow Wilson"), 20D); - presidents.put(new President("George W. Bush"), 21D); - presidents.put(new President("Franklin D. Roosevelt"), 20D); - presidents.put(new President("Ronald Reagan"), 20D); - presidents.put(new President("William J. Clinton"), 20D); - presidents.put(new President("Abraham Lincoln"), 19D); - presidents.put(new President("George Washington"), 18D); - - Message> message = MessageBuilder.withPayload(presidents).setHeader("redis_key", key).build(); - handler.handleMessage(message); - - assertThat(redisZset).hasSize(13); - - Set> entries = redisZset.rangeByScoreWithScores(18, 19); - assertThat(entries).hasSize(6); - RedisContainerTest.deletePresidents(redisConnectionFactory); - } - - @Test - void testZsetWithMapPayloadPojoAsSingleEntryHeaderKey() { - RedisContainerTest.deletePresidents(redisConnectionFactory); - String key = "presidents"; - RedisZSet> redisZset = - new DefaultRedisZSet<>(key, this.initTemplate(redisConnectionFactory, new RedisTemplate<>())); - - assertThat(redisZset).isEmpty(); - - RedisTemplate> template = this.initTemplate(redisConnectionFactory, new RedisTemplate<>()); - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(template); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setExtractPayloadElements(false); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - handler.afterPropertiesSet(); - - Map presidents = new HashMap<>(); - presidents.put(new President("John Adams"), 18D); - - presidents.put(new President("Barack Obama"), 21D); - presidents.put(new President("Thomas Jefferson"), 19D); - - Message> message = MessageBuilder.withPayload(presidents).setHeader("redis_key", key).build(); - handler.handleMessage(message); - - assertThat(redisZset).hasSize(1); - RedisContainerTest.deletePresidents(redisConnectionFactory); - } - - @Test - void testListWithMapKeyExpression() { - String key = "foo"; - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setMapKeyExpression(new LiteralExpression(key)); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - assertThatThrownBy(handler::afterPropertiesSet).isInstanceOf(IllegalStateException.class); - } - - @Test - void testSetWithMapKeyExpression() { - String key = "foo"; - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.SET); - handler.setMapKeyExpression(new LiteralExpression(key)); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - assertThatThrownBy(handler::afterPropertiesSet).isInstanceOf(IllegalStateException.class); - } - - @Test - void testZsetWithMapKeyExpression() { - String key = "foo"; - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.ZSET); - handler.setMapKeyExpression(new LiteralExpression(key)); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - assertThatThrownBy(handler::afterPropertiesSet).isInstanceOf(IllegalStateException.class); - } - - @Test - void testMapWithMapKeyExpression() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.MAP); - handler.setMapKeyExpression(new LiteralExpression(key)); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - try { - handler.afterPropertiesSet(); - } - catch (Exception e) { - fail("No exception expected:" + e.getMessage()); - } - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - @Test - void testPropertiesWithMapKeyExpression() { - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - String key = "foo"; - RedisStoreWritingMessageHandler handler = - new RedisStoreWritingMessageHandler(redisConnectionFactory); - handler.setKey(key); - handler.setCollectionType(CollectionType.PROPERTIES); - handler.setMapKeyExpression(new LiteralExpression(key)); - handler.setBeanFactory(TEST_INTEGRATION_CONTEXT); - try { - handler.afterPropertiesSet(); - } - catch (Exception e) { - fail("No exception expected:" + e.getMessage()); - } - RedisContainerTest.deleteKey(redisConnectionFactory, "foo"); - } - - private RedisTemplate initTemplate(RedisConnectionFactory rcf, RedisTemplate redisTemplate) { - redisTemplate.setConnectionFactory(rcf); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.afterPropertiesSet(); - return redisTemplate; - } - - private static class President implements Serializable { - - private static final long serialVersionUID = 1L; - - private String name; - - President(String name) { - this.name = name; - } - - @SuppressWarnings("unused") - public String getName() { - return name; - } - - @SuppressWarnings("unused") - public void setName(String name) { - this.name = name; - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests-context.xml deleted file mode 100644 index 247c6b99009..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests-context.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests.java deleted file mode 100644 index e7f8c990cf5..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.handler.DelayHandler; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.MessageGroupStore; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * - * @since 3.0 - */ -class DelayerHandlerRescheduleIntegrationTests implements RedisContainerTest { - - public static final String DELAYER_ID = "delayerWithRedisMS" + UUID.randomUUID(); - - @Test - void testDelayerHandlerRescheduleWithRedisMessageStore() throws Exception { - AbstractApplicationContext context = new ClassPathXmlApplicationContext( - "DelayerHandlerRescheduleIntegrationTests-context.xml", this.getClass()); - MessageChannel input = context.getBean("input", MessageChannel.class); - MessageGroupStore messageStore = context.getBean("messageStore", MessageGroupStore.class); - - String delayerMessageGroupId = DELAYER_ID + ".messageGroupId"; - - messageStore.removeMessageGroup(delayerMessageGroupId); - - Message message1 = MessageBuilder.withPayload("test1").build(); - input.send(message1); - Thread.sleep(10); - input.send(MessageBuilder.withPayload("test2").build()); - - // Emulate restart and check DB state before next start - // Interrupt taskScheduler as quickly as possible - ThreadPoolTaskScheduler taskScheduler = - (ThreadPoolTaskScheduler) IntegrationContextUtils.getTaskScheduler(context); - taskScheduler.shutdown(); - assertThat(taskScheduler.getScheduledExecutor().awaitTermination(10, TimeUnit.SECONDS)).isTrue(); - context.close(); - - assertThat(messageStore.getMessageGroupCount()).isEqualTo(1); - assertThat(messageStore.iterator().next().getGroupId()).isEqualTo(delayerMessageGroupId); - assertThat(messageStore.messageGroupSize(delayerMessageGroupId)).isEqualTo(2); - assertThat(messageStore.getMessageCountForAllMessageGroups()).isEqualTo(2); - MessageGroup messageGroup = messageStore.getMessageGroup(delayerMessageGroupId); - Message messageInStore = messageGroup.getMessages().iterator().next(); - Object payload = messageInStore.getPayload(); - - // INT-3049 - assertThat(payload).isInstanceOf(DelayHandler.DelayedMessageWrapper.class); - assertThat(((DelayHandler.DelayedMessageWrapper) payload).getOriginal()).isEqualTo(message1); - - context.refresh(); - - PollableChannel output = context.getBean("output", PollableChannel.class); - - Message message = output.receive(20000); - assertThat(message).isNotNull(); - - Object payload1 = message.getPayload(); - - message = output.receive(20000); - assertThat(message).isNotNull(); - Object payload2 = message.getPayload(); - assertThat(payload2).isNotSameAs(payload1); - - assertThat(messageStore.getMessageGroupCount()).isEqualTo(1); - int n = 0; - while (n++ < 300 && messageStore.messageGroupSize(delayerMessageGroupId) > 0) { - Thread.sleep(100); - } - assertThat(messageStore.messageGroupSize(delayerMessageGroupId)).isZero(); - - messageStore.removeMessageGroup(delayerMessageGroupId); - context.close(); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisChannelMessageStoreTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisChannelMessageStoreTests-context.xml deleted file mode 100644 index 105636895d8..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisChannelMessageStoreTests-context.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisChannelMessageStoreTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisChannelMessageStoreTests.java deleted file mode 100644 index 776f05e6af4..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisChannelMessageStoreTests.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import tools.jackson.databind.json.JsonMapper; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.serializer.GenericJackson3JsonRedisSerializer; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.MutableMessageBuilder; -import org.springframework.integration.support.json.JacksonMessagingUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 4.0 - * - */ -@SpringJUnitConfig -@DirtiesContext -class RedisChannelMessageStoreTests implements RedisContainerTest { - - @Autowired - private PollableChannel testChannel1; - - @Autowired - private PollableChannel testChannel2; - - @Autowired - private PollableChannel testChannel3; - - @Autowired - private PollableChannel testChannel4; - - @Autowired - private RedisChannelMessageStore cms; - - @Autowired - private RedisChannelMessageStore priorityCms; - - @BeforeEach - @AfterEach - void setUpTearDown() { - this.cms.removeMessageGroup("cms:testChannel1"); - this.cms.removeMessageGroup("cms:testChannel2"); - this.priorityCms.removeMessageGroup("priorityCms:testChannel3"); - this.priorityCms.removeMessageGroup("priorityCms:testChannel4"); - } - - @Test - void testChannel() { - for (int i = 0; i < 10; i++) { - this.testChannel1.send(new GenericMessage<>(i)); - } - assertThat(this.cms.getMessageGroupCount()).isEqualTo(1); - assertThat(this.cms.messageGroupSize("cms:testChannel1")).isEqualTo(10); - assertThat(this.cms.getMessageGroup("cms:testChannel1").size()).isEqualTo(10); - for (int i = 0; i < 10; i++) { - this.testChannel2.send(MutableMessageBuilder.withPayload(i).build()); - } - assertThat(this.cms.getMessageGroupCount()).isEqualTo(2); - assertThat(this.cms.messageGroupSize("cms:testChannel2")).isEqualTo(10); - assertThat(this.cms.getMessageGroup("cms:testChannel2").size()).isEqualTo(10); - assertThat(this.cms.getMessageCountForAllMessageGroups()).isEqualTo(20); - for (int i = 0; i < 10; i++) { - Message out = this.testChannel1.receive(0); - assertThat(out).isInstanceOf(GenericMessage.class); - assertThat(out.getPayload()).isEqualTo(i); - } - assertThat(this.testChannel1.receive(0)).isNull(); - for (int i = 0; i < 10; i++) { - Message out = this.testChannel2.receive(0); - assertThat(out.getClass().getName()).isEqualTo("org.springframework.integration.support.MutableMessage"); - assertThat(out.getPayload()).isEqualTo(i); - } - assertThat(this.testChannel2.receive(0)).isNull(); - assertThat(this.cms.getMessageGroupCount()).isZero(); - - for (int i = 0; i < 10; i++) { - this.testChannel1.send(new GenericMessage<>(i)); - } - assertThat(this.cms.getMessageGroupCount()).isEqualTo(1); - assertThat(this.cms.messageGroupSize("cms:testChannel1")).isEqualTo(10); - this.cms.removeMessageGroup("cms:testChannel1"); - assertThat(this.cms.getMessageGroupCount()).isZero(); - assertThat(this.cms.messageGroupSize("cms:testChannel1")).isZero(); - } - - @Test - void testPriority() { - for (int i = 0; i < 10; i++) { - this.testChannel3.send(MessageBuilder.withPayload(i).setPriority(i).build()); - //We need unique messages - this.testChannel3.send(MessageBuilder.withPayload(i).setPriority(i).build()); - } - this.testChannel3.send(MessageBuilder.withPayload(99).setPriority(199).build()); - this.testChannel3.send(MessageBuilder.withPayload(98).build()); - assertThat(this.priorityCms.getMessageGroupCount()).isEqualTo(1); - assertThat(this.priorityCms.messageGroupSize("priorityCms:testChannel3")).isEqualTo(22); - assertThat(this.priorityCms.getMessageCountForAllMessageGroups()).isEqualTo(22); - assertThat(this.priorityCms.getMessageGroup("priorityCms:testChannel3").size()).isEqualTo(22); - this.testChannel4.send(MessageBuilder.withPayload(98).build()); - this.testChannel4.send(MessageBuilder.withPayload(99).setPriority(5).build()); - assertThat(this.priorityCms.getMessageGroupCount()).isEqualTo(2); - assertThat(this.priorityCms.getMessageGroup("priorityCms:testChannel4").size()).isEqualTo(2); - assertThat(this.priorityCms.messageGroupSize("priorityCms:testChannel4")).isEqualTo(2); - assertThat(this.priorityCms.getMessageCountForAllMessageGroups()).isEqualTo(24); - for (int i = 0; i < 10; i++) { - Message m = this.testChannel3.receive(0); - assertThat(m).isNotNull(); - assertThat(new IntegrationMessageHeaderAccessor(m).getPriority()).isEqualTo(Integer.valueOf(9 - i)); - m = this.testChannel3.receive(0); - assertThat(m).isNotNull(); - assertThat(new IntegrationMessageHeaderAccessor(m).getPriority()).isEqualTo(Integer.valueOf(9 - i)); - } - Message m = this.testChannel3.receive(0); - assertThat(m).isNotNull(); - assertThat(new IntegrationMessageHeaderAccessor(m).getPriority()).isEqualTo(Integer.valueOf(199)); - assertThat(m.getPayload()).isEqualTo(99); - m = this.testChannel3.receive(0); - assertThat(m).isNotNull(); - assertThat(new IntegrationMessageHeaderAccessor(m).getPriority()).isNull(); - assertThat(m.getPayload()).isEqualTo(98); - assertThat(this.priorityCms.messageGroupSize("priorityCms:testChannel3")).isZero(); - - m = this.testChannel4.receive(0); - assertThat(m).isNotNull(); - assertThat(new IntegrationMessageHeaderAccessor(m).getPriority()).isEqualTo(Integer.valueOf(5)); - m = this.testChannel4.receive(0); - assertThat(m).isNotNull(); - assertThat(new IntegrationMessageHeaderAccessor(m).getPriority()).isNull(); - assertThat(this.priorityCms.getMessageGroupCount()).isZero(); - assertThat(this.priorityCms.getMessageCountForAllMessageGroups()).isZero(); - assertThat(this.testChannel3.receive(0)).isNull(); - assertThat(this.testChannel4.receive(0)).isNull(); - - for (int i = 0; i < 10; i++) { - this.testChannel3.send(new GenericMessage<>(i)); - } - assertThat(this.priorityCms.getMessageGroupCount()).isEqualTo(1); - assertThat(this.priorityCms.messageGroupSize("priorityCms:testChannel3")).isEqualTo(10); - this.priorityCms.removeMessageGroup("priorityCms:testChannel3"); - assertThat(this.priorityCms.getMessageGroupCount()).isZero(); - assertThat(this.priorityCms.messageGroupSize("priorityCms:testChannel3")).isZero(); - } - - @Test - void testJsonSerialization() { - RedisChannelMessageStore store = new RedisChannelMessageStore(RedisContainerTest.connectionFactory()); - JsonMapper mapper = JacksonMessagingUtils.messagingAwareMapper(); - GenericJackson3JsonRedisSerializer serializer = new GenericJackson3JsonRedisSerializer(mapper); - store.setValueSerializer(serializer); - - Message genericMessage = new GenericMessage<>(new Date()); - NullChannel testComponent = new NullChannel(); - testComponent.setBeanName("testChannel"); - genericMessage = MessageHistory.write(genericMessage, testComponent); - - String groupId = "jsonMessagesStore"; - - store.addMessageToGroup(groupId, genericMessage); - MessageGroup messageGroup = store.getMessageGroup(groupId); - assertThat(messageGroup.size()).isEqualTo(1); - List> messages = new ArrayList<>(messageGroup.getMessages()); - assertThat(messages.get(0)).isEqualTo(genericMessage); - assertThat(messages.get(0).getHeaders()).containsKeys(MessageHistory.HEADER_NAME); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageGroupStoreTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageGroupStoreTests.java deleted file mode 100644 index eb9d0b4983d..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageGroupStoreTests.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; -import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import junit.framework.AssertionFailedError; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import tools.jackson.databind.json.JsonMapper; - -import org.springframework.beans.BeanUtils; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson3JsonRedisSerializer; -import org.springframework.data.redis.serializer.SerializationException; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.NullChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.handler.DelayHandler; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.message.AdviceMessage; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.store.SimpleMessageGroup; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.MutableMessage; -import org.springframework.integration.support.json.JacksonMessagingUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; - -/** - * @author Oleg Zhurakousky - * @author Artem Bilan - * @author Gary Russell - * @author Artem Vozhdayenko - * @author Youbin Wu - */ -class RedisMessageGroupStoreTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - private final UUID groupId = UUID.randomUUID(); - - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - - @BeforeEach - @AfterEach - void setUpTearDown() { - StringRedisTemplate template = RedisContainerTest.createStringRedisTemplate(redisConnectionFactory); - template.delete(template.keys("MESSAGE_*")); - template.delete(template.keys("GROUP_OF_MESSAGES_*")); - } - - @Test - void testNonExistingEmptyMessageGroup() { - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup).isNotNull(); - assertThat(messageGroup).isInstanceOf(SimpleMessageGroup.class); - assertThat(messageGroup.size()).isZero(); - } - - @Test - void testMessageGroupUpdatedDateChangesWithEachAddedMessage() throws Exception { - Message message = new GenericMessage<>("Hello"); - MessageGroup messageGroup = store.addMessageToGroup(this.groupId, message); - assertThat(messageGroup.size()).isEqualTo(1); - long createdTimestamp = messageGroup.getTimestamp(); - long updatedTimestamp = messageGroup.getLastModified(); - assertThat(updatedTimestamp).isEqualTo(createdTimestamp); - Thread.sleep(10); - message = new GenericMessage<>("Hello"); - messageGroup = store.addMessageToGroup(this.groupId, message); - createdTimestamp = messageGroup.getTimestamp(); - updatedTimestamp = messageGroup.getLastModified(); - assertThat(updatedTimestamp > createdTimestamp).isTrue(); - - // make sure the store is properly rebuild from Redis - store = new RedisMessageStore(redisConnectionFactory); - - messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.size()).isEqualTo(2); - } - - @Test - void testMessageGroupWithAddedMessage() { - Message message = new GenericMessage<>("Hello"); - MessageGroup messageGroup = store.addMessageToGroup(this.groupId, message); - assertThat(messageGroup.size()).isEqualTo(1); - - // make sure the store is properly rebuild from Redis - store = new RedisMessageStore(redisConnectionFactory); - - messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.size()).isEqualTo(1); - } - - @Test - void testRemoveMessageGroup() { - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - Message message = new GenericMessage<>("Hello"); - messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message); - assertThat(messageGroup.size()).isEqualTo(1); - - store.removeMessageGroup(this.groupId); - MessageGroup messageGroupA = store.getMessageGroup(this.groupId); - assertThat(messageGroupA).isNotSameAs(messageGroup); - // assertEquals(0, messageGroupA.getMarked().size()); - assertThat(messageGroupA.getMessages().size()).isZero(); - assertThat(messageGroupA.size()).isZero(); - - // make sure the store is properly rebuild from Redis - store = new RedisMessageStore(redisConnectionFactory); - - messageGroup = store.getMessageGroup(this.groupId); - - assertThat(messageGroup.getMessages().size()).isZero(); - assertThat(messageGroup.size()).isZero(); - } - - @Test - void testCompleteMessageGroup() { - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - Message message = new GenericMessage<>("Hello"); - messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message); - store.completeGroup(messageGroup.getGroupId()); - messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.isComplete()).isTrue(); - } - - @Test - void testLastReleasedSequenceNumber() { - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - Message message = new GenericMessage<>("Hello"); - messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), message); - store.setLastReleasedSequenceNumberForGroup(messageGroup.getGroupId(), 5); - messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.getLastReleasedMessageSequenceNumber()).isEqualTo(5); - } - - @Test - void testRemoveMessageFromTheGroup() { - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - Message message = new GenericMessage<>("2"); - store.addMessagesToGroup(messageGroup.getGroupId(), new GenericMessage<>("1"), message); - messageGroup = store.addMessageToGroup(messageGroup.getGroupId(), new GenericMessage<>("3")); - assertThat(messageGroup.size()).isEqualTo(3); - - store.removeMessagesFromGroup(this.groupId, message); - messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.size()).isEqualTo(2); - - // make sure the store is properly rebuild from Redis - store = new RedisMessageStore(redisConnectionFactory); - - messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.size()).isEqualTo(2); - } - - @Test - void testWithMessageHistory() { - Message message = new GenericMessage<>("Hello"); - DirectChannel fooChannel = new DirectChannel(); - fooChannel.setBeanName("fooChannel"); - DirectChannel barChannel = new DirectChannel(); - barChannel.setBeanName("barChannel"); - - message = MessageHistory.write(message, fooChannel); - message = MessageHistory.write(message, barChannel); - store.addMessagesToGroup(this.groupId, message); - - message = store.getMessageGroup(this.groupId).getMessages().iterator().next(); - - MessageHistory messageHistory = MessageHistory.read(message); - assertThat(messageHistory).isNotNull(); - assertThat(messageHistory.size()).isEqualTo(2); - Properties fooChannelHistory = messageHistory.get(0); - assertThat(fooChannelHistory) - .containsEntry("name", "fooChannel") - .containsEntry("type", "channel"); - } - - @Test - void testRemoveNonExistingMessageFromTheGroup() { - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - store.addMessagesToGroup(messageGroup.getGroupId(), new GenericMessage<>("1")); - assertThatNoException() - .isThrownBy(() -> store.removeMessagesFromGroup(this.groupId, new GenericMessage<>("2"))); - } - - @Test - void testRemoveNonExistingMessageFromNonExistingTheGroup() { - assertThatNoException() - .isThrownBy(() -> store.removeMessagesFromGroup(this.groupId, new GenericMessage<>("2"))); - } - - @Test - void testMultipleInstancesOfGroupStore() { - RedisMessageStore store1 = new RedisMessageStore(redisConnectionFactory); - - RedisMessageStore store2 = new RedisMessageStore(redisConnectionFactory); - - store1.removeMessageGroup(this.groupId); - - Message message = new GenericMessage<>("1"); - store1.addMessagesToGroup(this.groupId, message); - MessageGroup messageGroup = store2.addMessageToGroup(this.groupId, new GenericMessage<>("2")); - - assertThat(messageGroup.getMessages().size()).isEqualTo(2); - - RedisMessageStore store3 = new RedisMessageStore(redisConnectionFactory); - - store3.removeMessagesFromGroup(this.groupId, message); - messageGroup = store3.getMessageGroup(this.groupId); - - assertThat(messageGroup.getMessages().size()).isEqualTo(1); - } - - @Test - void testIteratorOfMessageGroups() { - RedisMessageStore store1 = new RedisMessageStore(redisConnectionFactory); - RedisMessageStore store2 = new RedisMessageStore(redisConnectionFactory); - - store1.removeMessageGroup(this.groupId); - UUID group2 = UUID.randomUUID(); - store1.removeMessageGroup(group2); - UUID group3 = UUID.randomUUID(); - store1.removeMessageGroup(group3); - - store1.addMessagesToGroup(this.groupId, new GenericMessage<>("1")); - store2.addMessagesToGroup(group2, new GenericMessage<>("2")); - store1.addMessagesToGroup(group3, new GenericMessage<>("3"), new GenericMessage<>("3A")); - - Iterator messageGroups = store1.iterator(); - int counter = 0; - while (messageGroups.hasNext()) { - MessageGroup group = messageGroups.next(); - String groupId = (String) group.getGroupId(); - switch (groupId) { - case "1", "2" -> assertThat(group.getMessages().size()).isEqualTo(1); - case "3" -> assertThat(group.getMessages().size()).isEqualTo(2); - } - counter++; - } - assertThat(counter).isEqualTo(3); - - store2.removeMessageGroup(group3); - - messageGroups = store1.iterator(); - counter = 0; - while (messageGroups.hasNext()) { - messageGroups.next(); - counter++; - } - assertThat(counter).isEqualTo(2); - } - - @Test - @Disabled - void testConcurrentModifications() throws Exception { - final RedisMessageStore store1 = new RedisMessageStore(redisConnectionFactory); - final RedisMessageStore store2 = new RedisMessageStore(redisConnectionFactory); - - store1.removeMessageGroup(this.groupId); - - final Message message = new GenericMessage<>("1"); - - ExecutorService executor = null; - - final List failures = new ArrayList<>(); - - for (int i = 0; i < 100; i++) { - executor = Executors.newCachedThreadPool(); - - executor.execute(() -> { - MessageGroup group = store1.addMessageToGroup(this.groupId, message); - if (group.getMessages().size() != 1) { - failures.add("ADD"); - throw new AssertionFailedError("Failed on ADD"); - } - }); - executor.execute(() -> { - store2.removeMessagesFromGroup(this.groupId, message); - MessageGroup group = store2.getMessageGroup(this.groupId); - if (!group.getMessages().isEmpty()) { - failures.add("REMOVE"); - throw new AssertionFailedError("Failed on Remove"); - } - }); - - executor.shutdown(); - executor.awaitTermination(10, TimeUnit.SECONDS); - store2.removeMessagesFromGroup(1, message); // ensures that if ADD thread executed after REMOVE, the store is empty for the next cycle - } - assertThat(failures).isEmpty(); - } - - @Test - void testWithAggregatorWithShutdown() { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("redis-aggregator-config.xml", getClass()); - MessageChannel input = context.getBean("inputChannel", MessageChannel.class); - QueueChannel output = context.getBean("outputChannel", QueueChannel.class); - - Message m1 = MessageBuilder.withPayload("1") - .setSequenceNumber(1) - .setSequenceSize(3) - .setCorrelationId(this.groupId) - .build(); - - Message m2 = MessageBuilder.withPayload("2") - .setSequenceNumber(2) - .setSequenceSize(3) - .setCorrelationId(this.groupId) - .build(); - - input.send(m1); - assertThat(output.receive(10)).isNull(); - input.send(m2); - assertThat(output.receive(10)).isNull(); - - context.close(); - - context = new ClassPathXmlApplicationContext("redis-aggregator-config.xml", getClass()); - input = context.getBean("inputChannel", MessageChannel.class); - output = context.getBean("outputChannel", QueueChannel.class); - - Message m3 = MessageBuilder.withPayload("3") - .setSequenceNumber(3) - .setSequenceSize(3) - .setCorrelationId(this.groupId) - .build(); - - input.send(m3); - assertThat(output.receive(10000)).isNotNull(); - context.close(); - } - - @Test - void testAddAndRemoveMessagesFromMessageGroup() { - List> messages = new ArrayList<>(); - for (int i = 0; i < 25; i++) { - Message message = MessageBuilder.withPayload("foo").setCorrelationId(this.groupId).build(); - store.addMessagesToGroup(this.groupId, message); - messages.add(message); - } - MessageGroup group = store.getMessageGroup(this.groupId); - assertThat(group.size()).isEqualTo(25); - store.removeMessagesFromGroup(this.groupId, messages); - group = store.getMessageGroup(this.groupId); - assertThat(group.size()).isZero(); - store.removeMessageGroup(this.groupId); - } - - @Test - void testJsonSerialization() { - JsonMapper mapper = JacksonMessagingUtils.messagingAwareMapper(); - - GenericJackson3JsonRedisSerializer serializer = new GenericJackson3JsonRedisSerializer(mapper); - store.setValueSerializer(serializer); - - Message genericMessage = new GenericMessage<>(new Date()); - NullChannel testComponent = new NullChannel(); - testComponent.setBeanName("testChannel"); - genericMessage = MessageHistory.write(genericMessage, testComponent); - Message mutableMessage = new MutableMessage<>(UUID.randomUUID()); - Message adviceMessage = new AdviceMessage<>("foo", genericMessage); - ErrorMessage errorMessage = new ErrorMessage(new RuntimeException("test exception"), mutableMessage); - var delayedMessageWrapperConstructor = - BeanUtils.getResolvableConstructor(DelayHandler.DelayedMessageWrapper.class); - Message delayMessage = new GenericMessage<>( - BeanUtils.instantiateClass(delayedMessageWrapperConstructor, genericMessage, - System.currentTimeMillis())); - - store.addMessagesToGroup(this.groupId, - genericMessage, mutableMessage, adviceMessage, errorMessage, delayMessage); - - MessageGroup messageGroup = store.getMessageGroup(this.groupId); - assertThat(messageGroup.size()).isEqualTo(5); - List> messages = new ArrayList<>(messageGroup.getMessages()); - assertThat(messages.get(0)).isEqualTo(genericMessage); - assertThat(messages.get(0).getHeaders()).containsKeys(MessageHistory.HEADER_NAME); - assertThat(messages.get(1)).isEqualTo(mutableMessage); - assertThat(messages.get(2)).isEqualTo(adviceMessage); - Message errorMessageResult = messages.get(3); - assertThat(errorMessageResult.getHeaders()).isEqualTo(errorMessage.getHeaders()); - assertThat(errorMessageResult).isInstanceOf(ErrorMessage.class); - assertThat(((ErrorMessage) errorMessageResult).getOriginalMessage()) - .isEqualTo(errorMessage.getOriginalMessage()); - assertThat(((ErrorMessage) errorMessageResult).getPayload().getMessage()) - .isEqualTo(errorMessage.getPayload().getMessage()); - assertThat(messages.get(4)).isEqualTo(delayMessage); - - Message fooMessage = new GenericMessage<>(new Foo("foo")); - - assertThatExceptionOfType(SerializationException.class) - .isThrownBy(() -> - store.addMessageToGroup(this.groupId, fooMessage) - .getMessages() - .iterator() - .next()) - .withRootCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("The class with " + - "org.springframework.integration.redis.store.RedisMessageGroupStoreTests$Foo and name of " + - "org.springframework.integration.redis.store.RedisMessageGroupStoreTests$Foo " + - "is not in the trusted packages:"); - - mapper = JacksonMessagingUtils.messagingAwareMapper(getClass().getPackage().getName()); - - serializer = new GenericJackson3JsonRedisSerializer(mapper); - store.setValueSerializer(serializer); - - store.removeMessageGroup(this.groupId); - messageGroup = store.addMessageToGroup(this.groupId, fooMessage); - assertThat(messageGroup.size()).isEqualTo(1); - assertThat(messageGroup.getMessages().iterator().next()).isEqualTo(fooMessage); - - mapper = JacksonMessagingUtils.messagingAwareMapper("*"); - - serializer = new GenericJackson3JsonRedisSerializer(mapper); - store.setValueSerializer(serializer); - - store.removeMessageGroup(this.groupId); - messageGroup = store.addMessageToGroup(this.groupId, fooMessage); - assertThat(messageGroup.size()).isEqualTo(1); - assertThat(messageGroup.getMessages().iterator().next()).isEqualTo(fooMessage); - } - - @Test - public void sameMessageInTwoGroupsNotRemovedByFirstGroup() { - GenericMessage testMessage = new GenericMessage<>("test data"); - - store.addMessageToGroup("1", testMessage); - store.addMessageToGroup("2", testMessage); - - store.removeMessageGroup("1"); - - assertThat(store.getMessageCount()).isEqualTo(1); - - store.removeMessageGroup("2"); - - assertThat(store.getMessageCount()).isEqualTo(0); - } - - @Test - public void removeMessagesFromGroupDontRemoveSameMessageInOtherGroup() { - GenericMessage testMessage = new GenericMessage<>("test data"); - - store.addMessageToGroup("1", testMessage); - store.addMessageToGroup("2", testMessage); - - store.removeMessagesFromGroup("1", testMessage); - - assertThat(store.getMessageCount()).isEqualTo(1); - assertThat(store.messageGroupSize("1")).isEqualTo(0); - assertThat(store.messageGroupSize("2")).isEqualTo(1); - } - - @Test - public void testMessageGroupCondition() { - String groupId = "X"; - Message message = MessageBuilder.withPayload("foo").build(); - store.addMessagesToGroup(groupId, message); - store.setGroupCondition(groupId, "testCondition"); - assertThat(store.getMessageGroup(groupId).getCondition()).isEqualTo("testCondition"); - } - - private record Foo(String foo) { - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageStoreTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageStoreTests.java deleted file mode 100644 index e12c0e4b388..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageStoreTests.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.store; - -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.UUID; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.BoundValueOperations; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.util.Address; -import org.springframework.integration.redis.util.Person; -import org.springframework.integration.store.MessageGroup; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - */ -class RedisMessageStoreTests implements RedisContainerTest { - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnection() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @BeforeEach - @AfterEach - public void setUpTearDown() { - StringRedisTemplate template = RedisContainerTest.createStringRedisTemplate(redisConnectionFactory); - template.delete(template.keys("*MESSAGE_*")); - } - - @Test - void testGetNonExistingMessage() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - Message message = store.getMessage(UUID.randomUUID()); - assertThat(message).isNull(); - } - - @Test - void testGetMessageCountWhenEmpty() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - assertThat(store.getMessageCount()).isZero(); - } - - @Test - void testAddStringMessage() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - Message stringMessage = new GenericMessage<>("Hello Redis"); - Message storedMessage = store.addMessage(stringMessage); - assertThat(storedMessage).isSameAs(stringMessage); - } - - @Test - void testAddSerializableObjectMessage() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - Address address = new Address(); - address.setAddress("1600 Pennsylvania Av, Washington, DC"); - Person person = new Person(address, "Barak Obama"); - - Message objectMessage = new GenericMessage<>(person); - Message storedMessage = store.addMessage(objectMessage); - assertThat(storedMessage).isSameAs(objectMessage); - } - - @Test - void testAddNonSerializableObjectMessage() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - - Message objectMessage = new GenericMessage<>(new Foo()); - - assertThatThrownBy(() -> store.addMessage(objectMessage)).isInstanceOf(IllegalArgumentException.class); - } - - @SuppressWarnings("unchecked") - @Test - void testAddAndGetStringMessage() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - Message stringMessage = new GenericMessage<>("Hello Redis"); - store.addMessage(stringMessage); - Message retrievedMessage = (Message) store.getMessage(stringMessage.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isEqualTo("Hello Redis"); - } - - @SuppressWarnings("unchecked") - @Test - void testAddAndGetWithPrefix() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory, "foo"); - Message stringMessage = new GenericMessage<>("Hello Redis"); - store.addMessage(stringMessage); - Message retrievedMessage = (Message) store.getMessage(stringMessage.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isEqualTo("Hello Redis"); - - StringRedisTemplate template = RedisContainerTest.createStringRedisTemplate(redisConnectionFactory); - BoundValueOperations ops = - template.boundValueOps("foo" + "MESSAGE_" + stringMessage.getHeaders().getId()); - assertThat(ops.get()).isNotNull(); - } - - @SuppressWarnings("unchecked") - @Test - void testAddAndRemoveStringMessage() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - Message stringMessage = new GenericMessage<>("Hello Redis"); - store.addMessage(stringMessage); - Message retrievedMessage = (Message) store.removeMessage(stringMessage.getHeaders().getId()); - assertThat(retrievedMessage).isNotNull(); - assertThat(retrievedMessage.getPayload()).isEqualTo("Hello Redis"); - assertThat(store.getMessage(stringMessage.getHeaders().getId())).isNull(); - } - - @Test - void testWithMessageHistory() { - RedisMessageStore store = new RedisMessageStore(redisConnectionFactory); - - Message message = new GenericMessage<>("Hello"); - DirectChannel fooChannel = new DirectChannel(); - fooChannel.setBeanName("fooChannel"); - DirectChannel barChannel = new DirectChannel(); - barChannel.setBeanName("barChannel"); - - message = MessageHistory.write(message, fooChannel); - message = MessageHistory.write(message, barChannel); - store.addMessage(message); - message = store.getMessage(message.getHeaders().getId()); - MessageHistory messageHistory = MessageHistory.read(message); - assertThat(messageHistory).isNotNull(); - assertThat(messageHistory.size()).isEqualTo(2); - Properties fooChannelHistory = messageHistory.get(0); - assertThat(fooChannelHistory) - .containsEntry("name", "fooChannel") - .containsEntry("type", "channel"); - } - - @Test - void testAddAndRemoveMessagesFromMessageGroup() { - RedisMessageStore messageStore = new RedisMessageStore(redisConnectionFactory); - String groupId = "X"; - List> messages = new ArrayList<>(); - for (int i = 0; i < 25; i++) { - Message message = MessageBuilder.withPayload("foo").setCorrelationId(groupId).build(); - messageStore.addMessagesToGroup(groupId, message); - messages.add(message); - } - messageStore.removeMessagesFromGroup(groupId, messages); - MessageGroup group = messageStore.getMessageGroup(groupId); - assertThat(group.size()).isZero(); - messageStore.removeMessageGroup("X"); - } - - public static class Foo { - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/redis-aggregator-config.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/redis-aggregator-config.xml deleted file mode 100644 index 1fec8207ac3..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/redis-aggregator-config.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/Address.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/Address.java deleted file mode 100644 index d5a4466ea72..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/Address.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.util; - -import java.io.Serializable; -import java.util.Objects; - -@SuppressWarnings("serial") -public class Address implements Serializable { - - private String address; - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public Address() { - } - - public Address(String address) { - this.address = address; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Address address1 = (Address) o; - return Objects.equals(this.address, address1.address); - } - - @Override - public int hashCode() { - return Objects.hash(this.address); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/AggregatorWithRedisLocksTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/AggregatorWithRedisLocksTests-context.xml deleted file mode 100644 index c3cfea48f6a..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/AggregatorWithRedisLocksTests-context.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/AggregatorWithRedisLocksTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/AggregatorWithRedisLocksTests.java deleted file mode 100644 index 95a160cb181..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/AggregatorWithRedisLocksTests.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.util; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.aggregator.ReleaseStrategy; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.store.MessageGroup; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Artem Bilan - * @author Artem Vozhdayenko - * - * @since 4.0 - */ -@SpringJUnitConfig -@DirtiesContext -class AggregatorWithRedisLocksTests implements RedisContainerTest { - - @Autowired - private LatchingReleaseStrategy releaseStrategy; - - @Autowired - private MessageChannel in; - - @Autowired - private MessageChannel in2; - - @Autowired - private PollableChannel out; - - @Autowired - private RedisConnectionFactory redisConnectionFactory; - - private volatile Exception exception; - - private RedisTemplate template; - - @BeforeEach - @AfterEach - public void setup() { - this.template = this.createTemplate(); - Set keys = template.keys("aggregatorWithRedisLocksTests:*"); - for (String key : keys) { - template.delete(key); - } - } - - @Test - void testLockSingleGroup() throws Exception { - this.releaseStrategy.reset(1); - ExecutorService executorService = Executors.newCachedThreadPool(); - executorService.execute(asyncSend("foo", 1, 1)); - executorService.execute(asyncSend("bar", 2, 1)); - assertThat(this.releaseStrategy.latch2.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(this.template.keys("aggregatorWithRedisLocksTests:*")).hasSize(1); - this.releaseStrategy.latch1.countDown(); - assertThat(this.out.receive(10000)).isNotNull(); - assertThat(this.releaseStrategy.maxCallers.get()).isEqualTo(1); - this.assertNoLocksAfterTest(); - assertThat(this.exception) - .as("Unexpected exception:" + (this.exception != null ? this.exception.toString() : "")).isNull(); - executorService.shutdown(); - } - - @Test - void testLockThreeGroups() throws Exception { - this.releaseStrategy.reset(3); - ExecutorService executorService = Executors.newCachedThreadPool(); - executorService.execute(asyncSend("foo", 1, 1)); - executorService.execute(asyncSend("bar", 2, 1)); - executorService.execute(asyncSend("foo", 1, 2)); - executorService.execute(asyncSend("bar", 2, 2)); - executorService.execute(asyncSend("foo", 1, 3)); - executorService.execute(asyncSend("bar", 2, 3)); - assertThat(this.releaseStrategy.latch2.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(this.template.keys("aggregatorWithRedisLocksTests:*")).hasSize(3); - this.releaseStrategy.latch1.countDown(); - this.releaseStrategy.latch1.countDown(); - this.releaseStrategy.latch1.countDown(); - assertThat(this.out.receive(10000)).isNotNull(); - assertThat(this.out.receive(10000)).isNotNull(); - assertThat(this.out.receive(10000)).isNotNull(); - assertThat(this.releaseStrategy.maxCallers.get()).isEqualTo(3); - this.assertNoLocksAfterTest(); - assertThat(this.exception) - .as("Unexpected exception:" + (this.exception != null ? this.exception.toString() : "")).isNull(); - executorService.shutdown(); - } - - @RepeatedTest(10) - void testDistributedAggregator() throws Exception { - this.releaseStrategy.reset(1); - ExecutorService executorService = Executors.newCachedThreadPool(); - executorService.execute(asyncSend("foo", 1, 1)); - executorService.execute(() -> { - try { - in2.send(new GenericMessage<>("bar", stubHeaders(2, 2, 1))); - } - catch (Exception e) { - exception = e; - } - }); - assertThat(this.releaseStrategy.latch2.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(this.template.keys("aggregatorWithRedisLocksTests:*")).hasSize(1); - this.releaseStrategy.latch1.countDown(); - assertThat(this.out.receive(10000)).isNotNull(); - assertThat(this.releaseStrategy.maxCallers.get()).isEqualTo(1); - this.assertNoLocksAfterTest(); - assertThat(this.exception) - .as("Unexpected exception:" + (this.exception != null ? this.exception.toString() : "")).isNull(); - executorService.shutdown(); - } - - private void assertNoLocksAfterTest() throws Exception { - int n = 0; - while (n++ < 100 && this.template.keys("aggregatorWithRedisLocksTests:*").size() > 0) { - Thread.sleep(100); - } - assertThat(this.template.keys("aggregatorWithRedisLocksTests:*")).isEmpty(); - } - - private Runnable asyncSend(final String payload, final int sequence, final int correlation) { - return () -> { - try { - in.send(new GenericMessage<>(payload, stubHeaders(sequence, 2, correlation))); - } - catch (Exception e) { - exception = e; - } - }; - } - - private Map stubHeaders(int sequenceNumber, int sequenceSize, int correlationId) { - var headers = new HashMap(); - headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, sequenceNumber); - headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, sequenceSize); - headers.put(IntegrationMessageHeaderAccessor.CORRELATION_ID, correlationId); - return headers; - } - - private RedisTemplate createTemplate() { - var template = new RedisTemplate(); - template.setConnectionFactory(redisConnectionFactory); - template.setKeySerializer(new StringRedisSerializer()); - template.afterPropertiesSet(); - return template; - } - - public static class LatchingReleaseStrategy implements ReleaseStrategy { - - private volatile CountDownLatch latch1; - - private volatile CountDownLatch latch2; - - private volatile AtomicInteger callers; - - private volatile AtomicInteger maxCallers; - - @Override - public boolean canRelease(MessageGroup group) { - synchronized (this) { - this.callers.incrementAndGet(); - this.maxCallers.set(Math.max(this.maxCallers.get(), this.callers.get())); - } - this.latch2.countDown(); - try { - this.latch1.await(10, TimeUnit.SECONDS); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - this.callers.decrementAndGet(); - return group.size() > 1; - } - - public void reset(int expectedConcurrency) { - this.latch1 = new CountDownLatch(expectedConcurrency); - this.latch2 = new CountDownLatch(expectedConcurrency); - this.callers = new AtomicInteger(); - this.maxCallers = new AtomicInteger(); - } - - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/CustomJsonSerializer.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/CustomJsonSerializer.java deleted file mode 100644 index 9079bb67460..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/CustomJsonSerializer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.SerializationException; -import org.springframework.integration.mapping.InboundMessageMapper; -import org.springframework.integration.support.json.JacksonJsonMessageParser; -import org.springframework.integration.support.json.JsonInboundMessageMapper; -import org.springframework.messaging.Message; - -/** - * @author Artem Bilan - * @since 3.0 - */ -public class CustomJsonSerializer implements RedisSerializer> { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - private final InboundMessageMapper mapper = - new JsonInboundMessageMapper(String.class, new JacksonJsonMessageParser()); - - @Override - public byte[] serialize(Message message) throws SerializationException { - try { - return this.objectMapper.writeValueAsBytes(message); - } - catch (JsonProcessingException e) { - throw new SerializationException("Fail to serialize 'message' to json.", e); - } - } - - @Override - public Message deserialize(byte[] bytes) throws SerializationException { - try { - return mapper.toMessage(new String(bytes)); - } - catch (Exception e) { - throw new SerializationException("Fail to deserialize 'message' from json.", e); - } - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/Person.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/Person.java deleted file mode 100644 index 5109af706fc..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/Person.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.util; - -import java.io.Serializable; -import java.util.Objects; - -@SuppressWarnings("serial") -public class Person implements Serializable { - - private Address address; - - private String name; - - public Person(Address address, String name) { - this.address = address; - this.name = name; - } - - public Person() { - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Person person = (Person) o; - return Objects.equals(this.address, person.address) && - Objects.equals(this.name, person.name); - } - - @Override - public int hashCode() { - return Objects.hash(this.address, this.name); - } - -} diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/RedisLockRegistryTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/RedisLockRegistryTests.java deleted file mode 100644 index 13ca92169e8..00000000000 --- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/RedisLockRegistryTests.java +++ /dev/null @@ -1,1041 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.redis.util; - -import java.time.Duration; -import java.util.ConcurrentModificationException; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.integration.redis.RedisContainerTest; -import org.springframework.integration.redis.util.RedisLockRegistry.RedisLockType; -import org.springframework.integration.support.locks.DistributedLock; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; - -/** - * @author Gary Russell - * @author Konstantin Yakimov - * @author Artem Bilan - * @author Vedran Pavic - * @author Unseok Kim - * @author Artem Vozhdayenko - * @author Anton Gabov - * @author Eddie Cho - * @author Youbin Wu - * - * @since 4.0 - * - */ -class RedisLockRegistryTests implements RedisContainerTest { - - private final Log logger = LogFactory.getLog(getClass()); - - private final String registryKey = UUID.randomUUID().toString(); - - private final String registryKey2 = UUID.randomUUID().toString(); - - private static RedisConnectionFactory redisConnectionFactory; - - @BeforeAll - static void setupConnections() { - redisConnectionFactory = RedisContainerTest.connectionFactory(); - } - - @BeforeEach - @AfterEach - void setupShutDown() { - StringRedisTemplate template = this.createTemplate(); - template.delete(this.registryKey + ":*"); - template.delete(this.registryKey2 + ":*"); - } - - private StringRedisTemplate createTemplate() { - return new StringRedisTemplate(redisConnectionFactory); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testLock(RedisLockType testRedisLockType) { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 10; i++) { - Lock lock = registry.obtain("foo"); - lock.lock(); - try { - assertThat(getRedisLockRegistryLocks(registry)).hasSize(1); - } - finally { - lock.unlock(); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testLockWithCustomTtl(RedisLockType testRedisLockType) throws InterruptedException { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 100); - long sleepTimeLongerThanDefaultTTL = 200; - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 3; i++) { - DistributedLock lock = registry.obtain("foo"); - lock.lock(Duration.ofMillis(500)); - try { - assertThat(getRedisLockRegistryLocks(registry)).hasSize(1); - Thread.sleep(sleepTimeLongerThanDefaultTTL); - } - finally { - assertThatNoException().isThrownBy(lock::unlock); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTryLockWithCustomTtl(RedisLockType testRedisLockType) throws InterruptedException { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 100); - long sleepTimeLongerThanDefaultTTL = 200; - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 3; i++) { - DistributedLock lock = registry.obtain("foo"); - lock.tryLock(Duration.ofMillis(100), Duration.ofMillis(500)); - try { - assertThat(getRedisLockRegistryLocks(registry)).hasSize(1); - Thread.sleep(sleepTimeLongerThanDefaultTTL); - } - finally { - assertThatNoException().isThrownBy(lock::unlock); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testUnlockAfterLockStatusHasBeenExpired(RedisLockType testRedisLockType) throws InterruptedException { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 100); - registry.setRedisLockType(testRedisLockType); - Lock lock = registry.obtain("foo"); - lock.lock(); - Thread.sleep(200); - - assertThatThrownBy(lock::unlock).isInstanceOf(ConcurrentModificationException.class); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testLockInterruptibly(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 10; i++) { - Lock lock = registry.obtain("foo"); - lock.lockInterruptibly(); - try { - assertThat(getRedisLockRegistryLocks(registry)).hasSize(1); - } - finally { - lock.unlock(); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testReentrantLock(RedisLockType testRedisLockType) { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 10; i++) { - Lock lock1 = registry.obtain("foo"); - lock1.lock(); - try { - Lock lock2 = registry.obtain("foo"); - assertThat(lock2).isSameAs(lock1); - lock2.lock(); - try { - // just get the lock - } - finally { - lock2.unlock(); - } - } - finally { - lock1.unlock(); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testReentrantLockInterruptibly(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 10; i++) { - Lock lock1 = registry.obtain("foo"); - lock1.lockInterruptibly(); - try { - Lock lock2 = registry.obtain("foo"); - assertThat(lock2).isSameAs(lock1); - lock2.lockInterruptibly(); - try { - // just get the lock - } - finally { - lock2.unlock(); - } - } - finally { - lock1.unlock(); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTwoLocks(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - for (int i = 0; i < 10; i++) { - Lock lock1 = registry.obtain("foo"); - lock1.lockInterruptibly(); - try { - Lock lock2 = registry.obtain("bar"); - assertThat(lock2).isNotSameAs(lock1); - lock2.lockInterruptibly(); - try { - // just get the lock - } - finally { - lock2.unlock(); - } - } - finally { - lock1.unlock(); - } - } - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTwoThreadsSecondFailsToGetLock(RedisLockType testRedisLockType) throws Exception { - final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - final Lock lock1 = registry.obtain("foo"); - lock1.lockInterruptibly(); - final AtomicBoolean locked = new AtomicBoolean(); - final CountDownLatch latch = new CountDownLatch(1); - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Future result = executorService.submit(() -> { - Lock lock2 = registry.obtain("foo"); - locked.set(lock2.tryLock(200, TimeUnit.MILLISECONDS)); - latch.countDown(); - try { - lock2.unlock(); - } - catch (IllegalStateException ise) { - return ise; - } - return null; - }); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(locked.get()).isFalse(); - lock1.unlock(); - Object ise = result.get(10, TimeUnit.SECONDS); - assertThat(ise).isInstanceOf(IllegalStateException.class); - assertThat(((Exception) ise).getMessage()).contains("You do not own lock at"); - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - executorService.shutdown(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTwoThreads(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - Lock lock1 = registry.obtain("foo"); - AtomicBoolean locked = new AtomicBoolean(); - CountDownLatch latch1 = new CountDownLatch(1); - CountDownLatch latch2 = new CountDownLatch(1); - CountDownLatch latch3 = new CountDownLatch(1); - lock1.lockInterruptibly(); - assertThat(getRedisLockRegistryLocks(registry)).hasSize(1); - ExecutorService executorService = Executors.newSingleThreadExecutor(); - executorService.execute(() -> { - Lock lock2 = registry.obtain("foo"); - try { - latch1.countDown(); - lock2.lockInterruptibly(); - assertThat(getRedisLockRegistryLocks(registry)).hasSize(1); - latch2.await(10, TimeUnit.SECONDS); - locked.set(true); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - finally { - lock2.unlock(); - latch3.countDown(); - } - }); - assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(locked.get()).isFalse(); - lock1.unlock(); - latch2.countDown(); - assertThat(latch3.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(locked.get()).isTrue(); - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - executorService.shutdown(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTwoThreadsDifferentRegistries(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry1 = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry1.setRedisLockType(testRedisLockType); - RedisLockRegistry registry2 = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry2.setRedisLockType(testRedisLockType); - Lock lock1 = registry1.obtain("foo"); - AtomicBoolean locked = new AtomicBoolean(); - CountDownLatch latch1 = new CountDownLatch(1); - CountDownLatch latch2 = new CountDownLatch(1); - CountDownLatch latch3 = new CountDownLatch(1); - lock1.lockInterruptibly(); - assertThat(getRedisLockRegistryLocks(registry1)).hasSize(1); - ExecutorService executorService = Executors.newSingleThreadExecutor(); - executorService.execute(() -> { - Lock lock2 = registry2.obtain("foo"); - try { - latch1.countDown(); - lock2.lockInterruptibly(); - assertThat(getRedisLockRegistryLocks(registry2)).hasSize(1); - latch2.await(10, TimeUnit.SECONDS); - locked.set(true); - } - catch (InterruptedException e1) { - Thread.currentThread().interrupt(); - this.logger.error("Interrupted while locking: " + lock2, e1); - } - finally { - try { - lock2.unlock(); - latch3.countDown(); - } - catch (IllegalStateException e2) { - this.logger.error("Failed to unlock: " + lock2, e2); - } - } - }); - assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(locked.get()).isFalse(); - lock1.unlock(); - latch2.countDown(); - assertThat(latch3.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(locked.get()).isTrue(); - registry1.expireUnusedOlderThan(-1000); - registry2.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry1)).isEmpty(); - assertThat(getRedisLockRegistryLocks(registry2)).isEmpty(); - registry1.destroy(); - registry2.destroy(); - executorService.shutdown(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTwoThreadsWrongOneUnlocks(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - Lock lock = registry.obtain("foo"); - lock.lockInterruptibly(); - AtomicBoolean locked = new AtomicBoolean(); - CountDownLatch latch = new CountDownLatch(1); - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Future result = executorService.submit(() -> { - try { - lock.unlock(); - } - catch (IllegalStateException ise) { - latch.countDown(); - return ise; - } - return null; - }); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(locked.get()).isFalse(); - lock.unlock(); - Object ise = result.get(10, TimeUnit.SECONDS); - assertThat(ise).isInstanceOf(IllegalStateException.class); - assertThat(((Exception) ise).getMessage()).contains("You do not own lock at"); - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - executorService.shutdown(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testExpireTwoRegistries(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry1 = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 100); - registry1.setRedisLockType(testRedisLockType); - RedisLockRegistry registry2 = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 100); - registry2.setRedisLockType(testRedisLockType); - Lock lock1 = registry1.obtain("foo"); - Lock lock2 = registry2.obtain("foo"); - assertThat(lock1.tryLock()).isTrue(); - assertThat(lock2.tryLock()).isFalse(); - waitForExpire("foo"); - assertThat(lock2.tryLock()).isTrue(); - assertThat(lock1.tryLock()).isFalse(); - registry1.destroy(); - registry2.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testExceptionOnExpire(RedisLockType testRedisLockType) throws Exception { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 1); - registry.setRedisLockType(testRedisLockType); - Lock lock1 = registry.obtain("foo"); - assertThat(lock1.tryLock()).isTrue(); - waitForExpire("foo"); - assertThatThrownBy(lock1::unlock) - .isInstanceOf(ConcurrentModificationException.class) - .hasMessageContaining("Lock was released in the store due to expiration."); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testRenewalOnExpire(RedisLockType redisLockType) throws Exception { - long expireAfter = 300L; - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, expireAfter); - registry.setRenewalTaskScheduler(new SimpleAsyncTaskScheduler()); - registry.setRedisLockType(redisLockType); - Lock lock1 = registry.obtain("foo"); - assertThat(lock1.tryLock()).isTrue(); - Thread.sleep(expireAfter * 2); - lock1.unlock(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testEquals(RedisLockType testRedisLockType) { - RedisConnectionFactory connectionFactory = redisConnectionFactory; - RedisLockRegistry registry1 = new RedisLockRegistry(connectionFactory, this.registryKey); - registry1.setRedisLockType(testRedisLockType); - RedisLockRegistry registry2 = new RedisLockRegistry(connectionFactory, this.registryKey); - registry2.setRedisLockType(testRedisLockType); - RedisLockRegistry registry3 = new RedisLockRegistry(connectionFactory, this.registryKey2); - registry3.setRedisLockType(testRedisLockType); - - Lock lock1 = registry1.obtain("foo"); - Lock lock2 = registry1.obtain("foo"); - assertThat(lock2).isEqualTo(lock1); - lock1.lock(); - lock2.lock(); - assertThat(lock2).isEqualTo(lock1); - lock1.unlock(); - lock2.unlock(); - assertThat(lock2).isEqualTo(lock1); - - lock1 = registry1.obtain("foo"); - lock2 = registry2.obtain("foo"); - assertThat(lock2).isNotEqualTo(lock1); - lock1.lock(); - assertThat(lock2.tryLock()).isFalse(); - lock1.unlock(); - - lock1 = registry1.obtain("foo"); - lock2 = registry3.obtain("foo"); - assertThat(lock2).isNotEqualTo(lock1); - lock1.lock(); - lock2.lock(); - lock1.unlock(); - lock2.unlock(); - registry1.destroy(); - registry2.destroy(); - registry3.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testThreadLocalListLeaks(RedisLockType testRedisLockType) { - RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, 10000); - registry.setRedisLockType(testRedisLockType); - - for (int i = 0; i < 10; i++) { - registry.obtain("foo" + i); - } - assertThat(getRedisLockRegistryLocks(registry)).hasSize(10); - - for (int i = 0; i < 10; i++) { - Lock lock = registry.obtain("foo" + i); - lock.lock(); - } - assertThat(getRedisLockRegistryLocks(registry)).hasSize(10); - - for (int i = 0; i < 10; i++) { - Lock lock = registry.obtain("foo" + i); - lock.unlock(); - } - assertThat(getRedisLockRegistryLocks(registry)).hasSize(10); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testExpireNotChanged(RedisLockType testRedisLockType) throws Exception { - RedisConnectionFactory connectionFactory = redisConnectionFactory; - final RedisLockRegistry registry = new RedisLockRegistry(connectionFactory, this.registryKey, 10000); - registry.setRedisLockType(testRedisLockType); - - Lock lock = registry.obtain("foo"); - lock.lock(); - - Long expire = getExpire(registry, "foo"); - - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Future result = executorService.submit(() -> { - Lock lock2 = registry.obtain("foo"); - assertThat(lock2.tryLock()).isFalse(); - return null; - }); - result.get(); - assertThat(getExpire(registry, "foo")).isEqualTo(expire); - lock.unlock(); - registry.destroy(); - executorService.shutdown(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void concurrentObtainCapacityTest(RedisLockType testRedisLockType) throws InterruptedException { - final int KEY_CNT = 500; - final int CAPACITY_CNT = 179; - final int THREAD_CNT = 4; - - final CountDownLatch countDownLatch = new CountDownLatch(THREAD_CNT); - final RedisConnectionFactory connectionFactory = redisConnectionFactory; - final RedisLockRegistry registry = new RedisLockRegistry(connectionFactory, this.registryKey, 10000); - registry.setCacheCapacity(CAPACITY_CNT); - registry.setRedisLockType(testRedisLockType); - - final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_CNT); - - for (int i = 0; i < KEY_CNT; i++) { - int finalI = i; - executorService.submit(() -> { - countDownLatch.countDown(); - try { - countDownLatch.await(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - String keyId = "foo:" + finalI; - Lock obtain = registry.obtain(keyId); - obtain.lock(); - obtain.unlock(); - }); - } - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); - - //capacity limit test - assertThat(getRedisLockRegistryLocks(registry)).hasSize(CAPACITY_CNT); - - registry.expireUnusedOlderThan(-1000); - assertThat(getRedisLockRegistryLocks(registry)).isEmpty(); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void concurrentObtainRemoveOrderTest(RedisLockType testRedisLockType) throws InterruptedException { - final int THREAD_CNT = 2; - final int DUMMY_LOCK_CNT = 3; - - final int CAPACITY_CNT = THREAD_CNT; - - final CountDownLatch countDownLatch = new CountDownLatch(THREAD_CNT); - final RedisConnectionFactory connectionFactory = redisConnectionFactory; - final RedisLockRegistry registry = new RedisLockRegistry(connectionFactory, this.registryKey, 10000); - registry.setCacheCapacity(CAPACITY_CNT); - registry.setRedisLockType(testRedisLockType); - - final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_CNT); - final Queue remainLockCheckQueue = new LinkedBlockingQueue<>(); - - //Removed due to capcity limit - for (int i = 0; i < DUMMY_LOCK_CNT; i++) { - Lock obtainLock0 = registry.obtain("foo:" + i); - obtainLock0.lock(); - obtainLock0.unlock(); - } - - for (int i = DUMMY_LOCK_CNT; i < THREAD_CNT + DUMMY_LOCK_CNT; i++) { - int finalI = i; - executorService.submit(() -> { - countDownLatch.countDown(); - try { - countDownLatch.await(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - String keyId = "foo:" + finalI; - remainLockCheckQueue.offer(keyId); - Lock obtain = registry.obtain(keyId); - obtain.lock(); - obtain.unlock(); - }); - } - - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); - - assertThat(getRedisLockRegistryLocks(registry)).containsKeys( - remainLockCheckQueue.toArray(new String[0])); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void concurrentObtainAccessRemoveOrderTest(RedisLockType testRedisLockType) throws InterruptedException { - final int THREAD_CNT = 2; - final int DUMMY_LOCK_CNT = 3; - - final int CAPACITY_CNT = THREAD_CNT + 1; - final String REMAIN_DUMMY_LOCK_KEY = "foo:1"; - - final CountDownLatch countDownLatch = new CountDownLatch(THREAD_CNT); - final RedisConnectionFactory connectionFactory = redisConnectionFactory; - final RedisLockRegistry registry = new RedisLockRegistry(connectionFactory, this.registryKey, 10000); - registry.setCacheCapacity(CAPACITY_CNT); - registry.setRedisLockType(testRedisLockType); - - final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_CNT); - final Queue remainLockCheckQueue = new LinkedBlockingQueue<>(); - - //Removed due to capcity limit - for (int i = 0; i < DUMMY_LOCK_CNT; i++) { - Lock obtainLock0 = registry.obtain("foo:" + i); - obtainLock0.lock(); - obtainLock0.unlock(); - } - - Lock obtainLock0 = registry.obtain(REMAIN_DUMMY_LOCK_KEY); - obtainLock0.lock(); - obtainLock0.unlock(); - remainLockCheckQueue.offer(REMAIN_DUMMY_LOCK_KEY); - - for (int i = DUMMY_LOCK_CNT; i < THREAD_CNT + DUMMY_LOCK_CNT; i++) { - int finalI = i; - executorService.submit(() -> { - countDownLatch.countDown(); - try { - countDownLatch.await(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - String keyId = "foo:" + finalI; - remainLockCheckQueue.offer(keyId); - Lock obtain = registry.obtain(keyId); - obtain.lock(); - obtain.unlock(); - }); - } - - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); - - assertThat(getRedisLockRegistryLocks(registry)).containsKeys( - remainLockCheckQueue.toArray(new String[0])); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void setCapacityTest(RedisLockType testRedisLockType) { - final int CAPACITY_CNT = 4; - final RedisConnectionFactory connectionFactory = redisConnectionFactory; - final RedisLockRegistry registry = new RedisLockRegistry(connectionFactory, this.registryKey, 10000); - registry.setCacheCapacity(CAPACITY_CNT); - registry.setRedisLockType(testRedisLockType); - - registry.obtain("foo:1"); - registry.obtain("foo:2"); - registry.obtain("foo:3"); - - //capacity 4->3 - registry.setCacheCapacity(CAPACITY_CNT - 1); - - registry.obtain("foo:4"); - - assertThat(getRedisLockRegistryLocks(registry)).hasSize(3); - assertThat(getRedisLockRegistryLocks(registry)).containsKeys("foo:2", "foo:3", "foo:4"); - - //capacity 3->4 - registry.setCacheCapacity(CAPACITY_CNT); - registry.obtain("foo:5"); - assertThat(getRedisLockRegistryLocks(registry)).hasSize(4); - assertThat(getRedisLockRegistryLocks(registry)).containsKeys("foo:3", "foo:4", "foo:5"); - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void twoRedisLockRegistryTest(RedisLockType testRedisLockType) throws InterruptedException { - RedisLockRegistry registry1 = new RedisLockRegistry(redisConnectionFactory, registryKey, 1000000L); - registry1.setRedisLockType(testRedisLockType); - RedisLockRegistry registry2 = new RedisLockRegistry(redisConnectionFactory, registryKey, 1000000L); - registry2.setRedisLockType(testRedisLockType); - - String lockKey = "test-1"; - - Lock obtainLock_1 = registry1.obtain(lockKey); - Lock obtainLock_2 = registry2.obtain(lockKey); - - CountDownLatch registry1Lock = new CountDownLatch(1); - CountDownLatch endDownLatch = new CountDownLatch(2); - - CompletableFuture future1 = CompletableFuture.runAsync(() -> { - try { - obtainLock_1.lock(); - // for (int i = 0; i < 10; i++) { - // Thread.sleep(1000); - // } - registry1Lock.countDown(); - obtainLock_1.unlock(); - endDownLatch.countDown(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - }); - - CompletableFuture future2 = CompletableFuture.runAsync(() -> { - try { - registry1Lock.await(); - } - catch (InterruptedException ignore) { - } - obtainLock_2.lock(); - obtainLock_2.unlock(); - endDownLatch.countDown(); - }); - - endDownLatch.await(); - - assertThat(future1).isNotCompletedExceptionally(); - assertThat(future2).isNotCompletedExceptionally(); - registry1.destroy(); - registry2.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void multiRedisLockRegistryTest(RedisLockType testRedisLockType) throws InterruptedException, ExecutionException { - final String testKey = "testKey"; - final long expireAfter = 100000L; - final int lockRegistryNum = 10; - final ExecutorService executorService = Executors.newFixedThreadPool(lockRegistryNum * 2); - final AtomicInteger atomicInteger = new AtomicInteger(0); - final List> collect = IntStream.range(0, lockRegistryNum) - .mapToObj((num) -> new RedisLockRegistry( - redisConnectionFactory, registryKey, expireAfter)) - .map((registry) -> { - registry.setRedisLockType(testRedisLockType); - final Callable callable = () -> { - Lock obtain = registry.obtain(testKey); - obtain.lock(); - obtain.unlock(); - atomicInteger.incrementAndGet(); - return true; - }; - return callable; - }) - .collect(Collectors.toList()); - - final int testCnt = 3; - for (int i = 0; i < testCnt; i++) { - List> futures_1 = executorService.invokeAll(collect); - for (Future fu : futures_1) { - fu.get(); - } - } - - assertThat(atomicInteger.get()).isEqualTo(testCnt * lockRegistryNum); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void earlyWakeUpTest(RedisLockType testRedisLockType) throws InterruptedException { - final int THREAD_CNT = 2; - final String testKey = "testKey"; - - final CountDownLatch tryLockReady = new CountDownLatch(THREAD_CNT); - final CountDownLatch awaitTimeout = new CountDownLatch(THREAD_CNT); - final RedisLockRegistry registry1 = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry1.setRedisLockType(testRedisLockType); - final RedisLockRegistry registry2 = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry2.setRedisLockType(testRedisLockType); - final RedisLockRegistry registry3 = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry3.setRedisLockType(testRedisLockType); - - final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_CNT); - - Lock lock1 = registry1.obtain(testKey); - Lock lock2 = registry2.obtain(testKey); - Lock lock3 = registry3.obtain(testKey); - AtomicInteger expectOne = new AtomicInteger(); - - lock1.lock(); - executorService.submit(() -> { - try { - tryLockReady.countDown(); - boolean b = lock2.tryLock(10, TimeUnit.SECONDS); - awaitTimeout.countDown(); - if (b) { - expectOne.incrementAndGet(); - } - } - catch (InterruptedException ignore) { - } - }); - - executorService.submit(() -> { - try { - tryLockReady.countDown(); - boolean b = lock3.tryLock(10, TimeUnit.SECONDS); - awaitTimeout.countDown(); - if (b) { - expectOne.incrementAndGet(); - } - } - catch (InterruptedException ignore) { - } - }); - - assertThat(tryLockReady.await(10, TimeUnit.SECONDS)).isTrue(); - lock1.unlock(); - assertThat(awaitTimeout.await(1, TimeUnit.SECONDS)).isFalse(); - assertThat(expectOne.get()).isEqualTo(1); - executorService.shutdown(); - registry1.destroy(); - registry2.destroy(); - registry3.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testTwoThreadsRemoveAndObtainSameLockSimultaneously(RedisLockType testRedisLockType) throws Exception { - final int TEST_CNT = 200; - final long EXPIRATION_TIME_MILLIS = 10000; - final long LOCK_WAIT_TIME_MILLIS = 500; - final String testKey = "testKey"; - - final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(testRedisLockType); - - for (int i = 0; i < TEST_CNT; i++) { - final String lockKey = testKey + i; - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference lock1 = new AtomicReference<>(); - final AtomicReference lock2 = new AtomicReference<>(); - - Thread thread1 = new Thread(() -> { - try { - latch.await(); - // remove lock - registry.expireUnusedOlderThan(EXPIRATION_TIME_MILLIS); - // obtain new lock and try to acquire - Lock lock = registry.obtain(lockKey); - lock.tryLock(LOCK_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS); - lock.unlock(); - - lock1.set(lock); - } - catch (InterruptedException ignore) { - } - }); - - Thread thread2 = new Thread(() -> { - try { - latch.await(); - // remove lock - registry.expireUnusedOlderThan(EXPIRATION_TIME_MILLIS); - // obtain new lock and try to acquire - Lock lock = registry.obtain(lockKey); - lock.tryLock(LOCK_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS); - lock.unlock(); - - lock2.set(lock); - } - catch (InterruptedException ignore) { - } - }); - - thread1.start(); - thread2.start(); - latch.countDown(); - thread1.join(); - thread2.join(); - - // locks must be the same! - assertThat(lock1.get()).isEqualTo(lock2.get()); - } - - registry.destroy(); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testLockRenew(RedisLockType redisLockType) { - final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(redisLockType); - final Lock lock = registry.obtain("foo"); - - assertThat(lock.tryLock()).isTrue(); - try { - registry.renewLock("foo"); - } - finally { - lock.unlock(); - } - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testLockRenewLockNotOwned(RedisLockType redisLockType) { - final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(redisLockType); - registry.obtain("foo"); - - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> registry.renewLock("foo")); - } - - @ParameterizedTest - @EnumSource(RedisLockType.class) - void testLockRenewWithCustomTtl(RedisLockType redisLockType) throws InterruptedException { - final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - final RedisLockRegistry registryOfAnotherProcess = new RedisLockRegistry(redisConnectionFactory, this.registryKey); - registry.setRedisLockType(redisLockType); - registryOfAnotherProcess.setRedisLockType(redisLockType); - final DistributedLock lock = registry.obtain("foo"); - final Lock lockOfAnotherProcess = registryOfAnotherProcess.obtain("foo"); - long ttl = 100; - long sleepTimeLongerThanTtl = 110; - assertThat(lock.tryLock(Duration.ofMillis(100), Duration.ofMillis(ttl))).isTrue(); - try { - registry.renewLock("foo", Duration.ofSeconds(2)); - Thread.sleep(sleepTimeLongerThanTtl); - assertThat(lockOfAnotherProcess.tryLock(100, TimeUnit.MILLISECONDS)).isFalse(); - } - finally { - lock.unlock(); - } - registryOfAnotherProcess.destroy(); - } - - @Test - void testInitialiseWithCustomExecutor() { - RedisLockRegistry redisLockRegistry = new RedisLockRegistry(redisConnectionFactory, "registryKey"); - redisLockRegistry.setRedisLockType(RedisLockType.PUB_SUB_LOCK); - assertThatNoException().isThrownBy(() -> redisLockRegistry.setExecutor(mock())); - } - - private Long getExpire(RedisLockRegistry registry, String lockKey) { - StringRedisTemplate template = createTemplate(); - String registryKey = TestUtils.getPropertyValue(registry, "registryKey", String.class); - return template.getExpire(registryKey + ":" + lockKey); - } - - private void waitForExpire(String key) throws Exception { - StringRedisTemplate template = createTemplate(); - int n = 0; - while (n++ < 100 && template.keys(this.registryKey + ":" + key).size() > 0) { - Thread.sleep(100); - } - assertThat(n < 100).as(key + " key did not expire").isTrue(); - } - - @SuppressWarnings("unchecked") - private static Map getRedisLockRegistryLocks(RedisLockRegistry registry) { - return TestUtils.getPropertyValue(registry, "locks", Map.class); - } - -} diff --git a/spring-integration-redis/src/test/resources/log4j2-test.xml b/spring-integration-redis/src/test/resources/log4j2-test.xml deleted file mode 100644 index 6d8371fa2e4..00000000000 --- a/spring-integration-redis/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/AbstractRSocketConnector.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/AbstractRSocketConnector.java deleted file mode 100644 index feecf540375..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/AbstractRSocketConnector.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.SmartLifecycle; -import org.springframework.messaging.rsocket.RSocketStrategies; -import org.springframework.util.Assert; -import org.springframework.util.MimeType; - -/** - * A base connector container for common RSocket client and server functionality. - *

- * It accepts {@link IntegrationRSocketEndpoint} instances for mapping registration via an internal - * {@link IntegrationRSocketMessageHandler} or performs an auto-detection otherwise, when all beans are ready - * in the application context. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see IntegrationRSocketMessageHandler - */ -public abstract class AbstractRSocketConnector - implements ApplicationContextAware, InitializingBean, DisposableBean, SmartInitializingSingleton, - SmartLifecycle { - - protected final IntegrationRSocketMessageHandler rSocketMessageHandler; // NOSONAR - final - - private boolean autoStartup = true; - - private volatile boolean running; - - protected AbstractRSocketConnector(IntegrationRSocketMessageHandler rSocketMessageHandler) { - Assert.notNull(rSocketMessageHandler, "'rSocketMessageHandler' must not be null"); - this.rSocketMessageHandler = rSocketMessageHandler; - } - - /** - * Configure a {@link MimeType} for data exchanging. - * @param dataMimeType the {@link MimeType} to use. - */ - public void setDataMimeType(@Nullable MimeType dataMimeType) { - this.rSocketMessageHandler.setDefaultDataMimeType(dataMimeType); - } - - protected @Nullable MimeType getDataMimeType() { - return this.rSocketMessageHandler.getDefaultDataMimeType(); - } - - /** - * Configure a {@link MimeType} for metadata exchanging. - * Default to {@code "message/x.rsocket.composite-metadata.v0"}. - * @param metadataMimeType the {@link MimeType} to use. - */ - public void setMetadataMimeType(MimeType metadataMimeType) { - this.rSocketMessageHandler.setDefaultMetadataMimeType(metadataMimeType); - } - - protected MimeType getMetadataMimeType() { - return this.rSocketMessageHandler.getDefaultMetadataMimeType(); - } - - /** - * Configure a {@link RSocketStrategies} for data encoding/decoding. - * @param rsocketStrategies the {@link RSocketStrategies} to use. - */ - public void setRSocketStrategies(RSocketStrategies rsocketStrategies) { - this.rSocketMessageHandler.setRSocketStrategies(rsocketStrategies); - } - - public RSocketStrategies getRSocketStrategies() { - return this.rSocketMessageHandler.getRSocketStrategies(); - } - - /** - * Configure {@link IntegrationRSocketEndpoint} instances for mapping and handling requests. - * @param endpoints the {@link IntegrationRSocketEndpoint} instances for handling inbound requests. - * @see #addEndpoint(IntegrationRSocketEndpoint) - */ - public void setEndpoints(IntegrationRSocketEndpoint... endpoints) { - Assert.notNull(endpoints, "'endpoints' must not be null"); - for (IntegrationRSocketEndpoint endpoint : endpoints) { - addEndpoint(endpoint); - } - } - - /** - * Add an {@link IntegrationRSocketEndpoint} for mapping and handling RSocket requests. - * @param endpoint the {@link IntegrationRSocketEndpoint} to map. - */ - public void addEndpoint(IntegrationRSocketEndpoint endpoint) { - this.rSocketMessageHandler.addEndpoint(endpoint); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.rSocketMessageHandler.setApplicationContext(applicationContext); - } - - @Override - public void afterPropertiesSet() { - this.rSocketMessageHandler.afterPropertiesSet(); - } - - @Override - public void afterSingletonsInstantiated() { - this.rSocketMessageHandler.detectEndpoints(); - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - @Override - public boolean isAutoStartup() { - return this.autoStartup; - } - - @Override - public void start() { - if (!this.running) { - this.running = true; - doStart(); - } - } - - protected abstract void doStart(); - - @Override - public void stop() { - this.running = false; - } - - @Override - public boolean isRunning() { - return this.running; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ClientRSocketConnector.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ClientRSocketConnector.java deleted file mode 100644 index a18d6b03ce1..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ClientRSocketConnector.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import java.net.URI; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; - -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.netty.client.TcpClientTransport; -import io.rsocket.transport.netty.client.WebsocketClientTransport; - -import org.springframework.messaging.rsocket.RSocketConnectorConfigurer; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.util.Assert; -import org.springframework.util.MimeType; - -/** - * A client {@link AbstractRSocketConnector} extension to the RSocket connection. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see io.rsocket.core.RSocketConnector - * @see RSocketRequester - */ -public class ClientRSocketConnector extends AbstractRSocketConnector { - - private final ClientTransport clientTransport; - - private final Map setupMetadata = new LinkedHashMap<>(4); - - private RSocketConnectorConfigurer connectorConfigurer = (connector) -> { - }; - - @SuppressWarnings("NullAway.Init") - private Object setupData; - - @SuppressWarnings("NullAway.Init") - private String setupRoute; - - private Object[] setupRouteVars = new Object[0]; - - private boolean autoConnect; - - @SuppressWarnings("NullAway.Init") - private RSocketRequester rsocketRequester; - - /** - * Instantiate a connector based on the {@link TcpClientTransport}. - * @param host the TCP host to connect. - * @param port the TCP port to connect. - * @see #ClientRSocketConnector(ClientTransport) - */ - public ClientRSocketConnector(String host, int port) { - this(TcpClientTransport.create(host, port)); - } - - /** - * Instantiate a connector based on the {@link WebsocketClientTransport}. - * @param uri the WebSocket URI to connect. - * @see #ClientRSocketConnector(ClientTransport) - */ - public ClientRSocketConnector(URI uri) { - this(WebsocketClientTransport.create(uri)); - } - - /** - * Instantiate a connector based on the provided {@link ClientTransport}. - * @param clientTransport the {@link ClientTransport} to use. - * @see RSocketRequester.Builder#transport(ClientTransport) - */ - public ClientRSocketConnector(ClientTransport clientTransport) { - super(new IntegrationRSocketMessageHandler()); - Assert.notNull(clientTransport, "'clientTransport' must not be null"); - this.clientTransport = clientTransport; - } - - /** - * Callback to configure the {@code ClientRSocketFactory} directly. - * Note: this class adds extra {@link RSocketConnectorConfigurer} to the - * target {@link RSocketRequester} to populate a reference to an internal - * {@link IntegrationRSocketMessageHandler#responder()}. - * This overrides possible external - * {@link io.rsocket.core.RSocketConnector#acceptor(io.rsocket.SocketAcceptor)} - * @param connectorConfigurer the {@link RSocketConnectorConfigurer} to - * configure the {@link io.rsocket.core.RSocketConnector}. - * @since 5.2.6 - * @see RSocketRequester.Builder#rsocketConnector(RSocketConnectorConfigurer) - */ - public void setConnectorConfigurer(RSocketConnectorConfigurer connectorConfigurer) { - Assert.notNull(connectorConfigurer, "'connectorConfigurer' must not be null"); - this.connectorConfigurer = connectorConfigurer; - } - - /** - * Set the route for the setup payload. - * @param setupRoute the route to connect to. - * @see RSocketRequester.Builder#setupRoute(String, Object...) - */ - public void setSetupRoute(String setupRoute) { - Assert.notNull(setupRoute, "'setupRoute' must not be null"); - this.setupRoute = setupRoute; - } - - /** - * Set the variables for route template to expand with. - * @param setupRouteVars the route to connect to. - * @see RSocketRequester.Builder#setupRoute(String, Object...) - */ - public void setSetupRouteVariables(Object... setupRouteVars) { - Assert.notNull(setupRouteVars, "'setupRouteVars' must not be null"); - this.setupRouteVars = Arrays.copyOf(setupRouteVars, setupRouteVars.length); - } - - /** - * Add metadata to the setup payload. Composite metadata must be - * in use if this is called more than once or in addition to - * {@link #setSetupRoute(String)}. - * @param setupMetadata the map of metadata to use. - * @see RSocketRequester.Builder#setupMetadata(Object, MimeType) - */ - public void setSetupMetadata(Map setupMetadata) { - Assert.notNull(setupMetadata, "'setupMetadata' must not be null"); - this.setupMetadata.clear(); - this.setupMetadata.putAll(setupMetadata); - } - - /** - * Set the data for the setup payload. - * @param setupData the data for connect frame. - * @see RSocketRequester.Builder#setupData(Object) - */ - public void setSetupData(Object setupData) { - Assert.notNull(setupData, "'setupData' must not be null"); - this.setupData = setupData; - } - - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - - this.rsocketRequester = RSocketRequester.builder() - .dataMimeType(getDataMimeType()) - .metadataMimeType(getMetadataMimeType()) - .rsocketStrategies(getRSocketStrategies()) - .setupData(this.setupData) - .setupRoute(this.setupRoute, this.setupRouteVars) - .rsocketConnector(this.connectorConfigurer) - .rsocketConnector((connector) -> - connector.acceptor(this.rSocketMessageHandler.responder())) - .apply((builder) -> this.setupMetadata.forEach(builder::setupMetadata)) - .transport(this.clientTransport); - } - - @Override - public void afterSingletonsInstantiated() { - this.autoConnect = this.rSocketMessageHandler.detectEndpoints(); - } - - @Override - protected void doStart() { - if (this.autoConnect) { - connect(); - } - } - - @Override - public void destroy() { - this.rsocketRequester.rsocketClient().dispose(); - } - - /** - * Perform subscription into the RSocket server for incoming requests. - */ - public void connect() { - this.rsocketRequester.rsocketClient().source().subscribe(); - } - - /** - * Return the {@link RSocketRequester} this connector is built on. - * @return the {@link RSocketRequester} this connector is built on. - * @since 5.4 - */ - public RSocketRequester getRequester() { - return this.rsocketRequester; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/IntegrationRSocketEndpoint.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/IntegrationRSocketEndpoint.java deleted file mode 100644 index 89322dcdb2f..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/IntegrationRSocketEndpoint.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import org.springframework.messaging.ReactiveMessageHandler; - -/** - * A marker {@link ReactiveMessageHandler} extension interface for Spring Integration - * inbound endpoints. - * It is used as mapping predicate in the internal RSocket acceptor of the - * {@link AbstractRSocketConnector}. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see AbstractRSocketConnector - * @see org.springframework.integration.rsocket.inbound.RSocketInboundGateway - */ -public interface IntegrationRSocketEndpoint extends ReactiveMessageHandler { - - /** - * Obtain path patterns this {@link ReactiveMessageHandler} is going to be mapped onto. - * @return the path patterns for mapping. - */ - String[] getPath(); - - /** - * Obtain {@link RSocketInteractionModel}s - * this {@link ReactiveMessageHandler} is going to be mapped onto. - * Defaults to all the {@link RSocketInteractionModel}s. - * @return the interaction models for mapping. - * @since 5.2.2 - */ - default RSocketInteractionModel[] getInteractionModels() { - return RSocketInteractionModel.values(); - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/IntegrationRSocketMessageHandler.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/IntegrationRSocketMessageHandler.java deleted file mode 100644 index 7d45427ca6e..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/IntegrationRSocketMessageHandler.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -import io.rsocket.Payload; -import io.rsocket.frame.FrameType; -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.context.ApplicationContext; -import org.springframework.core.MethodParameter; -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.core.codec.Encoder; -import org.springframework.messaging.Message; -import org.springframework.messaging.ReactiveMessageHandler; -import org.springframework.messaging.handler.CompositeMessageCondition; -import org.springframework.messaging.handler.DestinationPatternsMessageCondition; -import org.springframework.messaging.handler.invocation.reactive.HandlerMethodArgumentResolver; -import org.springframework.messaging.handler.invocation.reactive.HandlerMethodReturnValueHandler; -import org.springframework.messaging.handler.invocation.reactive.SyncHandlerMethodArgumentResolver; -import org.springframework.messaging.rsocket.annotation.support.RSocketFrameTypeMessageCondition; -import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; -import org.springframework.messaging.rsocket.annotation.support.RSocketPayloadReturnValueHandler; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * The {@link RSocketMessageHandler} extension for Spring Integration needs. - *

- * This class adds an {@link IntegrationRSocketEndpoint} beans detection and registration functionality. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see RSocketMessageHandler - */ -class IntegrationRSocketMessageHandler extends RSocketMessageHandler { - - @SuppressWarnings("NullAway") // Reflection - private static final Method HANDLE_MESSAGE_METHOD = - ReflectionUtils.findMethod(ReactiveMessageHandler.class, "handleMessage", Message.class); - - protected final boolean messageMappingCompatible; // NOSONAR final - - IntegrationRSocketMessageHandler() { - this(false); - } - - IntegrationRSocketMessageHandler(boolean messageMappingCompatible) { - this.messageMappingCompatible = messageMappingCompatible; - if (!this.messageMappingCompatible) { - setHandlerPredicate((clazz) -> false); - } - } - - public boolean detectEndpoints() { - ApplicationContext applicationContext = getApplicationContext(); - boolean endpointsDetected = false; - if (applicationContext != null && getHandlerMethods().isEmpty()) { - Collection endpoints = - applicationContext.getBeansOfType(IntegrationRSocketEndpoint.class) - .values(); - for (IntegrationRSocketEndpoint endpoint : endpoints) { - addEndpoint(endpoint); - endpointsDetected = true; - } - } - return endpointsDetected; - } - - public void addEndpoint(IntegrationRSocketEndpoint endpoint) { - RSocketFrameTypeMessageCondition frameTypeMessageCondition = RSocketFrameTypeMessageCondition.EMPTY_CONDITION; - - RSocketInteractionModel[] interactionModels = endpoint.getInteractionModels(); - if (interactionModels.length > 0) { - frameTypeMessageCondition = - new RSocketFrameTypeMessageCondition( - Arrays.stream(interactionModels) - .map(RSocketInteractionModel::getFrameType) - .toArray(FrameType[]::new)); - } - registerHandlerMethod(endpoint, HANDLE_MESSAGE_METHOD, - new CompositeMessageCondition( - frameTypeMessageCondition, - new DestinationPatternsMessageCondition(endpoint.getPath(), obtainRouteMatcher()))); // NOSONAR - } - - @Override - protected List initArgumentResolvers() { - if (this.messageMappingCompatible) { - // Add argument resolver before parent initializes argument resolution - getArgumentResolverConfigurer().addCustomResolver(new MessageHandlerMethodArgumentResolver()); - return super.initArgumentResolvers(); - } - else { - return Collections.singletonList(new MessageHandlerMethodArgumentResolver()); - } - } - - @Override - @SuppressWarnings("unchecked") - protected List initReturnValueHandlers() { - HandlerMethodReturnValueHandler integrationRSocketPayloadReturnValueHandler = - new IntegrationRSocketPayloadReturnValueHandler((List>) getEncoders(), - getReactiveAdapterRegistry()); - if (this.messageMappingCompatible) { - List handlers = new ArrayList<>(); - handlers.add(integrationRSocketPayloadReturnValueHandler); - handlers.addAll(getReturnValueHandlerConfigurer().getCustomHandlers()); - return handlers; - } - else { - return Collections.singletonList(integrationRSocketPayloadReturnValueHandler); - } - } - - protected static final class MessageHandlerMethodArgumentResolver implements SyncHandlerMethodArgumentResolver { - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return Message.class.equals(parameter.getParameterType()); - } - - @Override - public Object resolveArgumentValue(MethodParameter parameter, Message message) { - return message; - } - - } - - protected static final class IntegrationRSocketPayloadReturnValueHandler extends RSocketPayloadReturnValueHandler { - - protected IntegrationRSocketPayloadReturnValueHandler(List> encoders, - ReactiveAdapterRegistry registry) { - - super(encoders, registry); - } - - @Override - public Mono handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, - Message message) { - - AtomicReference> responseReference = getResponseReference(message); - - if (returnValue == null && responseReference != null) { - return super.handleReturnValue(responseReference.get(), returnType, message); - } - else { - return super.handleReturnValue(returnValue, returnType, message); - } - } - - @SuppressWarnings("unchecked") - private static @Nullable AtomicReference> getResponseReference(Message message) { - Object headerValue = message.getHeaders().get(RESPONSE_HEADER); - Assert.state(headerValue == null || headerValue instanceof AtomicReference, "Expected AtomicReference"); - return (AtomicReference>) headerValue; - } - - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/RSocketConnectedEvent.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/RSocketConnectedEvent.java deleted file mode 100644 index 1b039559684..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/RSocketConnectedEvent.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import java.util.Map; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.integration.events.IntegrationEvent; -import org.springframework.messaging.rsocket.RSocketRequester; - -/** - * An {@link IntegrationEvent} to indicate that {@code RSocket} from the client is connected - * to the server. - *

- * This event can be used for mapping {@link RSocketRequester} to the client by the - * {@code headers} meta-data or connect payload {@code data}. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see IntegrationRSocketMessageHandler - */ -@SuppressWarnings("serial") -public class RSocketConnectedEvent extends IntegrationEvent { - - private final Map headers; - - private final DataBuffer data; - - private final RSocketRequester requester; - - public RSocketConnectedEvent(Object source, Map headers, DataBuffer data, - RSocketRequester requester) { - - super(source); - this.headers = headers; - this.data = data; - this.requester = requester; - } - - public Map getHeaders() { - return this.headers; - } - - public DataBuffer getData() { - return this.data; - } - - public RSocketRequester getRequester() { - return this.requester; - } - - @Override - public String toString() { - return "RSocketConnectedEvent{" + - "headers=" + this.headers + - ", data=" + this.data + - ", requester=" + this.requester + - '}'; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/RSocketInteractionModel.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/RSocketInteractionModel.java deleted file mode 100644 index 6a2e26979d5..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/RSocketInteractionModel.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import io.rsocket.frame.FrameType; - -/** - * The RSocket protocol interaction models. - * - * @author Artem Bilan - * - * @since 5.2.2 - * - * @see RSocket protocol official site - * @see FrameType - */ -public enum RSocketInteractionModel { - - /** - * The model for {@link io.rsocket.RSocket#fireAndForget} operation. - */ - fireAndForget(FrameType.REQUEST_FNF), - - /** - * The model for {@link io.rsocket.RSocket#requestResponse} operation. - */ - requestResponse(FrameType.REQUEST_RESPONSE), - - /** - * The model for {@link io.rsocket.RSocket#requestStream} operation. - */ - requestStream(FrameType.REQUEST_STREAM), - - /** - * The model for {@link io.rsocket.RSocket#requestChannel} operation. - */ - requestChannel(FrameType.REQUEST_CHANNEL); - - private final FrameType frameType; - - RSocketInteractionModel(FrameType frameType) { - this.frameType = frameType; - } - - public FrameType getFrameType() { - return this.frameType; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ServerRSocketConnector.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ServerRSocketConnector.java deleted file mode 100644 index 8fd8872b8c4..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ServerRSocketConnector.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -import io.rsocket.core.RSocketServer; -import io.rsocket.transport.ServerTransport; -import io.rsocket.transport.netty.server.CloseableChannel; -import io.rsocket.transport.netty.server.TcpServerTransport; -import io.rsocket.transport.netty.server.WebsocketServerTransport; -import org.jspecify.annotations.Nullable; -import reactor.core.Disposable; -import reactor.core.publisher.Mono; -import reactor.netty.http.server.HttpServer; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.messaging.rsocket.RSocketStrategies; -import org.springframework.util.Assert; -import org.springframework.util.MimeType; - -/** - * A server {@link AbstractRSocketConnector} extension to accept and manage client RSocket connections. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see io.rsocket.core.RSocketConnector - */ -public class ServerRSocketConnector extends AbstractRSocketConnector implements ApplicationEventPublisherAware { - - private final @Nullable ServerTransport serverTransport; - - private Consumer serverConfigurer = (rsocketServer) -> { - }; - - @SuppressWarnings("NullAway.Init") - private Mono serverMono; - - /** - * Instantiate a server connector based on a provided {@link ServerRSocketMessageHandler} - * with an assumption that RSocket server is created externally as well. - * All other options are ignored in favor of provided {@link ServerRSocketMessageHandler} - * and its external RSocket server configuration. - * @param serverRSocketMessageHandler the {@link ServerRSocketMessageHandler} to rely on. - * @since 5.2.1 - */ - public ServerRSocketConnector(ServerRSocketMessageHandler serverRSocketMessageHandler) { - super(serverRSocketMessageHandler); - this.serverTransport = null; - } - - /** - * Instantiate a server connector based on the {@link TcpServerTransport}. - * @param bindAddress the local address to bind TCP server onto. - * @param port the local TCP port to bind. - * @see #ServerRSocketConnector(ServerTransport) - */ - public ServerRSocketConnector(String bindAddress, int port) { - this(TcpServerTransport.create(bindAddress, port)); - } - - /** - * Instantiate a server connector based on the {@link WebsocketServerTransport}. - * @param server the {@link HttpServer} to use. - * @see #ServerRSocketConnector(ServerTransport) - */ - public ServerRSocketConnector(HttpServer server) { - this(WebsocketServerTransport.create(server)); - } - - /** - * Instantiate a server connector based on the provided {@link ServerTransport}. - * @param serverTransport the {@link ServerTransport} to make server based on. - */ - public ServerRSocketConnector(ServerTransport serverTransport) { - super(new ServerRSocketMessageHandler()); - Assert.notNull(serverTransport, "'serverTransport' must not be null"); - this.serverTransport = serverTransport; - } - - private ServerRSocketMessageHandler serverRSocketMessageHandler() { - return (ServerRSocketMessageHandler) this.rSocketMessageHandler; - } - - /** - * Provide a {@link Consumer} to configure the {@link RSocketServer}. - * @param serverConfigurer the {@link Consumer} to configure the {@link RSocketServer}. - * @since 5.2.6 - */ - public void setServerConfigurer(Consumer serverConfigurer) { - this.serverConfigurer = serverConfigurer; - } - - /** - * Configure a strategy to determine a key for the client {@link RSocketRequester} connected. - * Defaults to the {@code destination} to which a client is connected. - * @param clientRSocketKeyStrategy the {@link BiFunction} to determine a key for client {@link RSocketRequester}s. - */ - public void setClientRSocketKeyStrategy(BiFunction, - DataBuffer, Object> clientRSocketKeyStrategy) { - - if (this.serverTransport != null) { - serverRSocketMessageHandler().setClientRSocketKeyStrategy(clientRSocketKeyStrategy); - } - } - - @Override - public void setDataMimeType(@Nullable MimeType dataMimeType) { - if (this.serverTransport != null) { - super.setDataMimeType(dataMimeType); - } - } - - @Override - public void setMetadataMimeType(MimeType metadataMimeType) { - if (this.serverTransport != null) { - super.setMetadataMimeType(metadataMimeType); - } - } - - @Override - public void setRSocketStrategies(RSocketStrategies rsocketStrategies) { - if (this.serverTransport != null) { - super.setRSocketStrategies(rsocketStrategies); - } - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - if (this.serverTransport != null) { - super.setApplicationContext(applicationContext); - } - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - if (this.serverTransport != null) { - serverRSocketMessageHandler().setApplicationEventPublisher(applicationEventPublisher); - } - } - - @Override - public void afterPropertiesSet() { - if (this.serverTransport != null) { - super.afterPropertiesSet(); - RSocketServer rsocketServer = RSocketServer.create(); - this.serverConfigurer.accept(rsocketServer); - - this.serverMono = - rsocketServer - .acceptor(serverRSocketMessageHandler().responder()) - .bind(this.serverTransport) - .cache(); - } - } - - /** - * Return connected {@link RSocketRequester}s mapped by keys. - * @return connected {@link RSocketRequester}s mapped by keys. - * @see ServerRSocketMessageHandler#getClientRSocketRequesters() - */ - public Map getClientRSocketRequesters() { - return serverRSocketMessageHandler().getClientRSocketRequesters(); - } - - /** - * Return connected {@link RSocketRequester} mapped by key or null. - * @param key the mapping key. - * @return the {@link RSocketRequester} or null. - * @see ServerRSocketMessageHandler#getClientRSocketRequester(Object) - */ - public @Nullable RSocketRequester getClientRSocketRequester(Object key) { - return serverRSocketMessageHandler().getClientRSocketRequester(key); - } - - /** - * Return the port this internal server is bound or empty {@link Mono}. - * @return the port this internal server is bound or empty {@link Mono} - * if an external server is used. - */ - public Mono getBoundPort() { - if (this.serverTransport != null) { - return this.serverMono - .map((server) -> server.address().getPort()); - } - else { - return Mono.empty(); - } - } - - @Override - protected void doStart() { - if (this.serverTransport != null) { - this.serverMono.subscribe(); - } - } - - @Override - public void destroy() { - if (this.serverTransport != null) { - this.serverMono - .doOnNext(Disposable::dispose) - .subscribe(); - } - } - - @Override - public void afterSingletonsInstantiated() { - super.afterSingletonsInstantiated(); - serverRSocketMessageHandler().registerHandleConnectionSetupMethod(); - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ServerRSocketMessageHandler.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ServerRSocketMessageHandler.java deleted file mode 100644 index 0c5edcf1aa1..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/ServerRSocketMessageHandler.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket; - -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aot.hint.annotation.Reflective; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.handler.CompositeMessageCondition; -import org.springframework.messaging.handler.DestinationPatternsMessageCondition; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.messaging.rsocket.annotation.support.RSocketFrameTypeMessageCondition; -import org.springframework.messaging.rsocket.annotation.support.RSocketRequesterMethodArgumentResolver; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * An {@link IntegrationRSocketMessageHandler} extension for RSocket service side. - *

- * In a plain Spring Integration application instances of this class are created by the - * {@link ServerRSocketConnector} internally and a new RSocket server is started over there. - * When an existing RSocket server is in use, an instance of this class has to be - * provided as a {@link #responder()} into that server and a {@link ServerRSocketConnector} - * should accept the same instance as a delegate. - *

- * With a {@link #messageMappingCompatible} option this class also handles - * {@link org.springframework.messaging.handler.annotation.MessageMapping} methods, - * covering both Spring Integration and standard - * {@link org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler} - * functionality. - * - * @author Artem Bilan - * - * @since 5.2.1 - */ -public class ServerRSocketMessageHandler extends IntegrationRSocketMessageHandler - implements ApplicationEventPublisherAware { - - @SuppressWarnings("NullAway") // Reflection - private static final Method HANDLE_CONNECTION_SETUP_METHOD = - ReflectionUtils.findMethod(ServerRSocketMessageHandler.class, "handleConnectionSetup", Message.class); - - private final Map clientRSocketRequesters = new HashMap<>(); - - private BiFunction, DataBuffer, Object> clientRSocketKeyStrategy = - (headers, data) -> data.toString(StandardCharsets.UTF_8); - - private @Nullable ApplicationEventPublisher applicationEventPublisher; - - /** - * Create an service side RSocket message handler instance for delegating - * to {@link IntegrationRSocketEndpoint} beans and collect {@link RSocketRequester}s - * from client connections. - */ - public ServerRSocketMessageHandler() { - this(false); - } - - /** - * Create an service side RSocket message handler instance for delegating - * to {@link IntegrationRSocketEndpoint} beans and collect {@link RSocketRequester}s - * from client connections. - * When {@code messageMappingCompatible == true}, this class also handles - * {@link org.springframework.messaging.handler.annotation.MessageMapping} methods - * as it is done by the standard - * {@link org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler}. - * @param messageMappingCompatible whether handle also - * {@link org.springframework.messaging.handler.annotation.MessageMapping}. - */ - public ServerRSocketMessageHandler(boolean messageMappingCompatible) { - super(messageMappingCompatible); - } - - /** - * Configure a {@link BiFunction} to extract a key for mapping connected {@link RSocketRequester}s. - * Defaults to the {@code destination} a client is connected. - * @param clientRSocketKeyStrategy the {@link BiFunction} to use. - */ - public void setClientRSocketKeyStrategy( - BiFunction, DataBuffer, Object> clientRSocketKeyStrategy) { - - Assert.notNull(clientRSocketKeyStrategy, "'clientRSocketKeyStrategy' must not be null"); - this.clientRSocketKeyStrategy = clientRSocketKeyStrategy; - } - - /** - * Get connected {@link RSocketRequester}s mapped by the keys from the connect messages. - * @return the map of connected {@link RSocketRequester}s. - * @see #setClientRSocketKeyStrategy - */ - public Map getClientRSocketRequesters() { - return Collections.unmodifiableMap(this.clientRSocketRequesters); - } - - /** - * Obtain a connected {@link RSocketRequester} mapped by provided key or null. - * @param key the key for mapped {@link RSocketRequester} if any. - * @return the mapped {@link RSocketRequester} or null. - */ - public @Nullable RSocketRequester getClientRSocketRequester(Object key) { - return this.clientRSocketRequesters.get(key); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - void registerHandleConnectionSetupMethod() { - registerHandlerMethod(this, HANDLE_CONNECTION_SETUP_METHOD, - new CompositeMessageCondition( - RSocketFrameTypeMessageCondition.CONNECT_CONDITION, - new DestinationPatternsMessageCondition(new String[] {"*"}, obtainRouteMatcher()))); - } - - @SuppressWarnings("unused") - @Reflective - private void handleConnectionSetup(Message connectMessage) { - DataBuffer dataBuffer = connectMessage.getPayload(); - MessageHeaders messageHeaders = connectMessage.getHeaders(); - Object rsocketRequesterKey = this.clientRSocketKeyStrategy.apply(messageHeaders, dataBuffer); - RSocketRequester rsocketRequester = - messageHeaders.get(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, - RSocketRequester.class); - Assert.notNull(rsocketRequester, "'rsocketRequester' can not be null"); - this.clientRSocketRequesters.put(rsocketRequesterKey, rsocketRequester); - RSocketConnectedEvent rSocketConnectedEvent = - new RSocketConnectedEvent(this, messageHeaders, dataBuffer, rsocketRequester); // NOSONAR - if (this.applicationEventPublisher != null) { - this.applicationEventPublisher.publishEvent(rSocketConnectedEvent); - } - else { - if (logger.isInfoEnabled()) { - logger.info("The RSocket has been connected: " + rSocketConnectedEvent); - } - } - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParser.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParser.java deleted file mode 100644 index 40f812e6443..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParser.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.config; - -import java.util.Arrays; -import java.util.List; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.integration.config.xml.AbstractInboundGatewayParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.rsocket.inbound.RSocketInboundGateway; - -/** - * Parser for the <inbound-gateway/> element of the 'rsocket' namespace. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class RSocketInboundGatewayParser extends AbstractInboundGatewayParser { - - private static final List NON_ELIGIBLE_ATTRIBUTES = - Arrays.asList("path", - "rsocket-strategies", - "rsocket-connector", - "request-element-type"); - - @Override - protected Class getBeanClass(Element element) { - return RSocketInboundGateway.class; - } - - @Override - protected boolean isEligibleAttribute(String attributeName) { - return !NON_ELIGIBLE_ATTRIBUTES.contains(attributeName) && super.isEligibleAttribute(attributeName); - } - - @Override - protected void doPostProcess(BeanDefinitionBuilder builder, Element element) { - builder.addConstructorArgValue(element.getAttribute("path")); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-element-type", - "requestElementClass"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "rsocket-strategies", - "rSocketStrategies"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "rsocket-connector", - "RSocketConnector"); - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketNamespaceHandler.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketNamespaceHandler.java deleted file mode 100644 index c0c3823a36c..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketNamespaceHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.config; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * Namespace handler for Spring Integration XML configuration for RSocket support. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class RSocketNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - public void init() { - registerBeanDefinitionParser("inbound-gateway", new RSocketInboundGatewayParser()); - registerBeanDefinitionParser("outbound-gateway", new RSocketOutboundGatewayParser()); - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParser.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParser.java deleted file mode 100644 index 8572ac91dfb..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParser.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.core.Conventions; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway; - -/** - * Parser for the 'outbound-gateway' element of the rsocket namespace. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class RSocketOutboundGatewayParser extends AbstractConsumerEndpointParser { - - @Override - protected String getInputChannelAttributeName() { - return "request-channel"; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RSocketOutboundGateway.class); - BeanDefinition routeExpression = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("route", "route-expression", - parserContext, element, true); - builder.addConstructorArgValue(routeExpression); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "client-rsocket-connector", - "clientRSocketConnector"); - populateValueOrExpressionIfAny(builder, element, parserContext, "interaction-model"); - populateValueOrExpressionIfAny(builder, element, parserContext, "command"); - populateValueOrExpressionIfAny(builder, element, parserContext, "publisher-element-type"); - populateValueOrExpressionIfAny(builder, element, parserContext, "expected-response-type"); - populateValueOrExpressionIfAny(builder, element, parserContext, "metadata"); - return builder; - } - - private static void populateValueOrExpressionIfAny(BeanDefinitionBuilder builder, Element element, - ParserContext parserContext, String valueAttributeName) { - - String expressionAttributeName = valueAttributeName + "-expression"; - - BeanDefinition expression = - IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(valueAttributeName, - expressionAttributeName, parserContext, element, false); - if (expression != null) { - builder.addPropertyValue(Conventions.attributeNameToPropertyName(expressionAttributeName), expression); - } - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/package-info.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/package-info.java deleted file mode 100644 index bd5fc50bc15..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for RSocket XML namespace parsing and configuration support. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.rsocket.config; diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSocketInboundGatewaySpec.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSocketInboundGatewaySpec.java deleted file mode 100644 index f6b04d2853f..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSocketInboundGatewaySpec.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.dsl; - -import org.springframework.core.ResolvableType; -import org.springframework.integration.dsl.MessagingGatewaySpec; -import org.springframework.integration.rsocket.AbstractRSocketConnector; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.integration.rsocket.inbound.RSocketInboundGateway; -import org.springframework.messaging.rsocket.RSocketStrategies; - -/** - * The {@link MessagingGatewaySpec} implementation for the {@link RSocketInboundGateway}. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class RSocketInboundGatewaySpec extends MessagingGatewaySpec { - - protected RSocketInboundGatewaySpec(String... path) { - super(new RSocketInboundGateway(path)); - } - - /** - * Configure a set of {@link RSocketInteractionModel} the endpoint is going to be mapped onto. - * @param interactionModels the {@link RSocketInteractionModel}s for mapping. - * @return the spec. - * @since 5.2.2 - * @see RSocketInboundGateway#setInteractionModels(RSocketInteractionModel...) - */ - public RSocketInboundGatewaySpec interactionModels(RSocketInteractionModel... interactionModels) { - this.target.setInteractionModels(interactionModels); - return this; - } - - /** - * Configure an {@link RSocketStrategies} instead of a default one. - * @param rsocketStrategies the {@link RSocketStrategies} to use. - * @return the spec - * @see RSocketInboundGateway#setRSocketStrategies(RSocketStrategies) - */ - public RSocketInboundGatewaySpec rsocketStrategies(RSocketStrategies rsocketStrategies) { - this.target.setRSocketStrategies(rsocketStrategies); - return this; - } - - /** - * Provide an {@link AbstractRSocketConnector} reference for an explicit endpoint mapping. - * @param rsocketConnector the {@link AbstractRSocketConnector} to use. - * @return the spec - * @see RSocketInboundGateway#setRSocketConnector(AbstractRSocketConnector) - */ - public RSocketInboundGatewaySpec rsocketConnector(AbstractRSocketConnector rsocketConnector) { - this.target.setRSocketConnector(rsocketConnector); - return this; - } - - /** - * Specify a type of payload to be generated when the inbound RSocket request - * content is read by the converters/encoders. - * @param requestElementType The payload type. - * @return the spec - * @see RSocketInboundGateway#setRequestElementType(ResolvableType) - */ - public RSocketInboundGatewaySpec requestElementType(ResolvableType requestElementType) { - this.target.setRequestElementType(requestElementType); - return this; - } - - /** - * Configure an option to decode an incoming {@link reactor.core.publisher.Flux} - * as a single unit or each its event separately. - * @param decodeFluxAsUnit decode incoming {@link reactor.core.publisher.Flux} - * as a single unit or each event separately. - * @return the spec - * @since 5.3 - * @see RSocketInboundGateway#setDecodeFluxAsUnit(boolean) - */ - public RSocketInboundGatewaySpec decodeFluxAsUnit(boolean decodeFluxAsUnit) { - this.target.setDecodeFluxAsUnit(decodeFluxAsUnit); - return this; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSocketOutboundGatewaySpec.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSocketOutboundGatewaySpec.java deleted file mode 100644 index bd326d0eef2..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSocketOutboundGatewaySpec.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.dsl; - -import java.util.Map; -import java.util.function.Function; - -import org.springframework.expression.Expression; -import org.springframework.integration.dsl.MessageHandlerSpec; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway; -import org.springframework.messaging.Message; -import org.springframework.util.MimeType; - -/** - * The {@link MessageHandlerSpec} implementation for the {@link RSocketOutboundGateway}. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class RSocketOutboundGatewaySpec extends MessageHandlerSpec { - - protected RSocketOutboundGatewaySpec(String route, Object... routeVariables) { - this.target = new RSocketOutboundGateway(route, routeVariables); - } - - protected RSocketOutboundGatewaySpec(Expression routeExpression) { - this.target = new RSocketOutboundGateway(routeExpression); - } - - /** - * Configure a {@link ClientRSocketConnector} for client side requests based on the connection - * provided by the {@link ClientRSocketConnector#getRequester()}. - * @param clientRSocketConnector the {@link ClientRSocketConnector} to use. - * @return the spec - * @see RSocketOutboundGateway#setClientRSocketConnector(ClientRSocketConnector) - */ - public RSocketOutboundGatewaySpec clientRSocketConnector(ClientRSocketConnector clientRSocketConnector) { - this.target.setClientRSocketConnector(clientRSocketConnector); - return this; - } - - /** - * Configure an {@link RSocketInteractionModel} for the RSocket request type. - * @param interactionModel the {@link RSocketInteractionModel} to use. - * @return the spec - * @since 5.2.2 - * @see RSocketOutboundGateway#setInteractionModel(RSocketInteractionModel) - */ - public RSocketOutboundGatewaySpec interactionModel(RSocketInteractionModel interactionModel) { - return interactionModel(new ValueExpression<>(interactionModel)); - } - - /** - * Configure a {@link Function} to evaluate an {@link RSocketInteractionModel} - * for the RSocket request type at runtime against a request message. - * @param interactionModelFunction the {@code Function} to use. - * @param

the expected request message payload type. - * @return the spec - * @since 5.2.2 - * @see RSocketOutboundGateway#setInteractionModelExpression(Expression) - */ - public

RSocketOutboundGatewaySpec interactionModel(Function, ?> interactionModelFunction) { - return interactionModel(new FunctionExpression<>(interactionModelFunction)); - } - - /** - * Configure a SpEL expression to evaluate an {@link RSocketInteractionModel} - * for the RSocket request type at runtime against a request message. - * @param interactionModelExpression the SpEL expression to use. - * @return the spec - * @since 5.2.2 - * @see RSocketOutboundGateway#setInteractionModelExpression(Expression) - */ - public RSocketOutboundGatewaySpec interactionModel(String interactionModelExpression) { - return interactionModel(PARSER.parseExpression(interactionModelExpression)); - } - - /** - * Configure a SpEL expression to evaluate an {@link RSocketInteractionModel} - * for the RSocket request type at runtime against a request message. - * @param interactionModelExpression the SpEL expression to use. - * @return the spec - * @since 5.2.2 - * @see RSocketOutboundGateway#setInteractionModelExpression(Expression) - */ - public RSocketOutboundGatewaySpec interactionModel(Expression interactionModelExpression) { - this.target.setInteractionModelExpression(interactionModelExpression); - return this; - } - - /** - * Configure a type for a request {@link org.reactivestreams.Publisher} elements. - * @param publisherElementType the type of the request {@link org.reactivestreams.Publisher} elements. - * @return the spec - * @see RSocketOutboundGateway#setPublisherElementType(Class) - */ - public RSocketOutboundGatewaySpec publisherElementType(Class publisherElementType) { - return publisherElementType(new ValueExpression<>(publisherElementType)); - } - - /** - * Configure a {@link Function} to evaluate a request {@link org.reactivestreams.Publisher} - * elements type at runtime against a request message. - * @param publisherElementTypeFunction the {@code Function} to evaluate a type for the request - * {@link org.reactivestreams.Publisher} elements. - * @param

the expected request message payload type. - * @return the spec - * @see RSocketOutboundGateway#setPublisherElementTypeExpression(Expression) - */ - public

RSocketOutboundGatewaySpec publisherElementType(Function, ?> publisherElementTypeFunction) { - return publisherElementType(new FunctionExpression<>(publisherElementTypeFunction)); - } - - /** - * Configure a SpEL expression to evaluate a request {@link org.reactivestreams.Publisher} - * elements type at runtime against a request message. - * @param publisherElementTypeExpression the expression to evaluate a type for the request - * {@link org.reactivestreams.Publisher} elements. - * @return the spec - * @see RSocketOutboundGateway#setPublisherElementTypeExpression(Expression) - */ - public RSocketOutboundGatewaySpec publisherElementType(String publisherElementTypeExpression) { - return publisherElementType(PARSER.parseExpression(publisherElementTypeExpression)); - } - - /** - * Configure a SpEL expression to evaluate a request {@link org.reactivestreams.Publisher} - * elements type at runtime against a request message. - * @param publisherElementTypeExpression the expression to evaluate a type for the request - * {@link org.reactivestreams.Publisher} elements. - * @return the spec - * @see RSocketOutboundGateway#setPublisherElementTypeExpression(Expression) - */ - public RSocketOutboundGatewaySpec publisherElementType(Expression publisherElementTypeExpression) { - this.target.setPublisherElementTypeExpression(publisherElementTypeExpression); - return this; - } - - /** - * Specify the expected response type for the RSocket response. - * @param expectedResponseType The expected type. - * @return the spec - * @see RSocketOutboundGateway#setExpectedResponseType(Class) - */ - public RSocketOutboundGatewaySpec expectedResponseType(Class expectedResponseType) { - return expectedResponseType(new ValueExpression<>(expectedResponseType)); - } - - /** - * Specify the {@link Function} to determine the type for the RSocket response. - * @param expectedResponseTypeFunction The expected response type {@code Function}. - * @param

the expected request message payload type. - * @return the spec - * @see RSocketOutboundGateway#setExpectedResponseTypeExpression(Expression) - */ - public

RSocketOutboundGatewaySpec expectedResponseType(Function, ?> expectedResponseTypeFunction) { - return expectedResponseType(new FunctionExpression<>(expectedResponseTypeFunction)); - } - - /** - * Specify the {@link Expression} to determine the type for the RSocket response. - * @param expectedResponseTypeExpression The expected response type expression. - * @return the spec - * @see RSocketOutboundGateway#setExpectedResponseTypeExpression(Expression) - */ - public RSocketOutboundGatewaySpec expectedResponseType(String expectedResponseTypeExpression) { - return expectedResponseType(PARSER.parseExpression(expectedResponseTypeExpression)); - } - - /** - * Specify an {@link Expression} to determine the type for the RSocket response. - * @param expectedResponseTypeExpression The expected response type expression. - * @return the spec - * @see RSocketOutboundGateway#setExpectedResponseTypeExpression(Expression) - */ - public RSocketOutboundGatewaySpec expectedResponseType(Expression expectedResponseTypeExpression) { - this.target.setExpectedResponseTypeExpression(expectedResponseTypeExpression); - return this; - } - - /** - * Configure a {@link Function} to evaluate a metadata as a {@code Map} - * for RSocket request against request message. - * @param metadataFunction the {@code Function} to use. - * @param

the expected request message payload type. - * @return the spec - * @see RSocketOutboundGateway#setMetadataExpression(Expression) - */ - public

RSocketOutboundGatewaySpec metadata(Function, Map> metadataFunction) { - return metadata(new FunctionExpression<>(metadataFunction)); - } - - /** - * Configure a SpEL expression to evaluate a metadata as a {@code Map} - * for the RSocket request against request message. - * @param metadataExpression the SpEL expression to use. - * @return the spec - * @see RSocketOutboundGateway#setMetadataExpression(Expression) - */ - public RSocketOutboundGatewaySpec metadata(String metadataExpression) { - return metadata(PARSER.parseExpression(metadataExpression)); - } - - /** - * Configure a SpEL expression to evaluate a metadata as a {@code Map} - * for the RSocket request type at runtime against a request message. - * @param metadataExpression the SpEL expression to use. - * @return the spec - * @see RSocketOutboundGateway#setMetadataExpression(Expression) - */ - public RSocketOutboundGatewaySpec metadata(Expression metadataExpression) { - this.target.setMetadataExpression(metadataExpression); - return this; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSockets.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSockets.java deleted file mode 100644 index 9f7cfc41ab4..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/RSockets.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.dsl; - -import java.util.function.Function; - -import org.springframework.expression.Expression; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.messaging.Message; - -/** - * The RSocket components Factory. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public final class RSockets { - - /** - * Create an {@link RSocketOutboundGatewaySpec} builder for request-reply gateway - * based on provided {@code route} and optional variables to expand route template. - * @param route the {@code route} to send requests. - * @param routeVariables the variables to expand route template. - * @return the RSocketOutboundGatewaySpec instance - */ - public static RSocketOutboundGatewaySpec outboundGateway(String route, Object... routeVariables) { - return new RSocketOutboundGatewaySpec(route, routeVariables); - } - - /** - * Create an {@link RSocketOutboundGatewaySpec} builder for request-reply gateway - * based on provided {@code Function} to evaluate target {@code route} against request message. - * @param routeFunction the {@code Function} to evaluate {@code route} at runtime. - * @param

the expected payload type. - * @return the RSocketOutboundGatewaySpec instance - */ - public static

RSocketOutboundGatewaySpec outboundGateway(Function, ?> routeFunction) { - return outboundGateway(new FunctionExpression<>(routeFunction)); - } - - /** - * Create an {@link RSocketOutboundGatewaySpec} builder for request-reply gateway - * based on provided SpEL {@link Expression} to evaluate target {@code route} against request message. - * @param routeExpression the SpEL {@link Expression} to evaluate {@code route} at runtime. - * @return the RSocketOutboundGatewaySpec instance - */ - public static RSocketOutboundGatewaySpec outboundGateway(Expression routeExpression) { - return new RSocketOutboundGatewaySpec(routeExpression); - } - - /** - * Create an {@link RSocketInboundGatewaySpec} builder for request-reply reactive gateway - * based on the provided {@code path} array for mapping. - * @param path the path mapping URIs (e.g. "/myPath.do"). - * @return the RSocketInboundGatewaySpec instance - */ - public static RSocketInboundGatewaySpec inboundGateway(String... path) { - return new RSocketInboundGatewaySpec(path); - } - - private RSockets() { - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/package-info.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/package-info.java deleted file mode 100644 index de45227c390..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides RSocket Components support for Spring Integration Java DSL. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.rsocket.dsl; diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/inbound/RSocketInboundGateway.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/inbound/RSocketInboundGateway.java deleted file mode 100644 index 1da485bf518..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/inbound/RSocketInboundGateway.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.inbound; - -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; - -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.util.context.ContextView; - -import org.springframework.core.ReactiveAdapter; -import org.springframework.core.ResolvableType; -import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.integration.IntegrationMessageHeaderAccessor; -import org.springframework.integration.gateway.MessagingGatewaySupport; -import org.springframework.integration.rsocket.AbstractRSocketConnector; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.IntegrationRSocketEndpoint; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.handler.invocation.reactive.HandlerMethodReturnValueHandler; -import org.springframework.messaging.rsocket.RSocketStrategies; -import org.springframework.messaging.rsocket.annotation.support.RSocketPayloadReturnValueHandler; -import org.springframework.util.Assert; -import org.springframework.util.MimeType; - -/** - * The {@link MessagingGatewaySupport} implementation for the {@link IntegrationRSocketEndpoint}. - * Represents an inbound endpoint for RSocket requests. - *

- * May be configured with the {@link AbstractRSocketConnector} for mapping registration. - * Or existing {@link AbstractRSocketConnector} bean(s) will perform detection automatically. - *

- * An inbound {@link DataBuffer} (either single or as a {@link Publisher} element) is - * converted to the target expected type which can be configured by the - * {@link #setRequestElementClass} or {@link #setRequestElementType(ResolvableType)}. - * If it is not configured, then target type is determined by the {@code contentType} header: - * If it is a {@code text}, then target type is {@link String}, otherwise - {@code byte[]}. - *

- * An inbound {@link Publisher} is used as is in the message to send payload. - * It is a target application responsibility to process that payload any possible way. - *

- * A reply payload is encoded to the {@link Flux} according a type of the payload or a - * {@link Publisher} element type. - * - * @author Artem Bilan - * - * @since 5.2 - */ -public class RSocketInboundGateway extends MessagingGatewaySupport implements IntegrationRSocketEndpoint { - - private final String[] path; - - private RSocketInteractionModel[] interactionModels = RSocketInteractionModel.values(); - - private RSocketStrategies rsocketStrategies = RSocketStrategies.create(); - - private @Nullable AbstractRSocketConnector rsocketConnector; - - private @Nullable ResolvableType requestElementType; - - private boolean decodeFluxAsUnit; - - /** - * Instantiate based on the provided path patterns to map this endpoint for incoming RSocket requests. - * @param pathArg the mapping patterns to use. - */ - public RSocketInboundGateway(String... pathArg) { - Assert.notNull(pathArg, "'pathArg' must not be null"); - this.path = Arrays.copyOf(pathArg, pathArg.length); - } - - /** - * Configure an {@link RSocketStrategies} instead of a default one. - * Note: if {@link AbstractRSocketConnector} is provided, then its - * {@link RSocketStrategies} have a precedence. - * @param rsocketStrategies the {@link RSocketStrategies} to use. - * @see RSocketStrategies#builder - */ - public void setRSocketStrategies(RSocketStrategies rsocketStrategies) { - Assert.notNull(rsocketStrategies, "'rsocketStrategies' must not be null"); - this.rsocketStrategies = rsocketStrategies; - } - - /** - * Provide an {@link AbstractRSocketConnector} reference for an explicit endpoint mapping. - * @param rsocketConnector the {@link AbstractRSocketConnector} to use. - */ - public void setRSocketConnector(AbstractRSocketConnector rsocketConnector) { - Assert.notNull(rsocketConnector, "'rsocketConnector' must not be null"); - this.rsocketConnector = rsocketConnector; - } - - /** - * Configure a set of {@link RSocketInteractionModel} this endpoint is mapped onto. - * @param interactionModelsArg the {@link RSocketInteractionModel}s for mapping. - * @since 5.2.2 - */ - public void setInteractionModels(RSocketInteractionModel... interactionModelsArg) { - Assert.notNull(interactionModelsArg, "'interactionModelsArg' must not be null"); - this.interactionModels = Arrays.copyOf(interactionModelsArg, interactionModelsArg.length); - } - - @Override - public RSocketInteractionModel[] getInteractionModels() { - return Arrays.copyOf(this.interactionModels, this.interactionModels.length); - } - - /** - * Get an array of the path patterns this endpoint is mapped onto. - * @return the mapping path - */ - public String[] getPath() { - return Arrays.copyOf(this.path, this.path.length); - } - - /** - * Specify a type of payload to be generated when the inbound RSocket request - * content is read by the encoders. - * By default this value is null which means at runtime any "text" Content-Type will - * result in String while all others default to {@code byte[].class}. - * @param requestElementClass The payload type. - */ - public void setRequestElementClass(Class requestElementClass) { - setRequestElementType(ResolvableType.forClass(requestElementClass)); - } - - /** - * Specify the type of payload to be generated when the inbound RSocket request - * content is read by the converters/encoders. - * By default, this value is null which means at runtime any "text" Content-Type will - * result in String while all others default to {@code byte[].class}. - * @param requestElementType The payload type. - */ - public void setRequestElementType(ResolvableType requestElementType) { - this.requestElementType = requestElementType; - } - - /** - * Configure an option to decode an incoming {@link Flux} as a single unit or each its event separately. - * Defaults to {@code false} for consistency with Spring Messaging {@code @MessageMapping}. - * The target {@link Flux} decoding logic depends on the {@link Decoder} selected. - * For example a {@link org.springframework.core.codec.StringDecoder} requires a new line separator to - * be present in the stream to indicate a byte buffer end. - * @param decodeFluxAsUnit decode incoming {@link Flux} as a single unit or each event separately. - * @since 5.3 - * @see Decoder#decode(Publisher, ResolvableType, MimeType, java.util.Map) - */ - public void setDecodeFluxAsUnit(boolean decodeFluxAsUnit) { - this.decodeFluxAsUnit = decodeFluxAsUnit; - } - - @Override - protected void onInit() { - super.onInit(); - AbstractRSocketConnector rsocketConnectorToUse = this.rsocketConnector; - if (rsocketConnectorToUse != null) { - rsocketConnectorToUse.addEndpoint(this); - this.rsocketStrategies = rsocketConnectorToUse.getRSocketStrategies(); - } - } - - @Override - protected void doStart() { - super.doStart(); - if (this.rsocketConnector instanceof ClientRSocketConnector clientRSocketConnector) { - clientRSocketConnector.connect(); - } - } - - @Override - public Mono handleMessage(Message requestMessage) { - if (!isRunning()) { - return Mono.error(new MessageDeliveryException(requestMessage, - "The RSocket Inbound Gateway '" + getComponentName() + "' is stopped; " + - "service for path(s) " + Arrays.toString(this.path) + " is not available at the moment.")); - } - - Mono> requestMono = decodeRequestMessage(requestMessage); - AtomicReference replyTo = getReplyToHeader(requestMessage); - if (replyTo != null) { - return requestMono - .flatMap(this::sendAndReceiveMessageReactive) - .flatMap((replyMessage) -> { - Flux reply = createReply(replyMessage.getPayload(), requestMessage); - replyTo.set(reply); - return Mono.empty(); - }); - } - else { - return requestMono - .flatMap((message) -> - Mono.deferContextual((context) -> - Mono.just(message) - .handle((messageToSend, sink) -> - send(messageWithReactorContextIfAny(messageToSend, context))))); - } - } - - private Message messageWithReactorContextIfAny(Message message, ContextView context) { - if (!context.isEmpty()) { - return getMessageBuilderFactory() - .fromMessage(message) - .setHeader(IntegrationMessageHeaderAccessor.REACTOR_CONTEXT, context) - .build(); - } - return message; - } - - private Mono> decodeRequestMessage(Message requestMessage) { - Object data = decodePayload(requestMessage); - if (data == null) { - return Mono.just(requestMessage); - } - else { - return Mono.just(data) - .map((payload) -> - MessageBuilder.withPayload(payload) - .copyHeaders(requestMessage.getHeaders()) - .build()); - } - } - - @SuppressWarnings("unchecked") - @Nullable - private Object decodePayload(Message requestMessage) { - ResolvableType elementType; - MimeType mimeType = requestMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class); - if (this.requestElementType == null) { - elementType = - mimeType != null && "text".equals(mimeType.getType()) - ? ResolvableType.forClass(String.class) - : ResolvableType.forClass(byte[].class); - } - else { - elementType = this.requestElementType; - } - - Object payload = requestMessage.getPayload(); - - // The MessagingRSocket logic ensures that we can have only a single DataBuffer payload or Flux. - Decoder decoder = this.rsocketStrategies.decoder(elementType, mimeType); - if (payload instanceof DataBuffer dataBuffer) { - return decoder.decode(dataBuffer, elementType, mimeType, null); - } - else if (this.decodeFluxAsUnit) { - return decoder.decode((Publisher) payload, elementType, mimeType, null); - } - else { - return Flux.from((Publisher) payload) - .handle((buffer, synchronousSink) -> { - Object value = decoder.decode(buffer, elementType, mimeType, null); - if (value != null) { - synchronousSink.next(value); - } - }); - } - } - - private Flux createReply(Object reply, Message requestMessage) { - MessageHeaders requestMessageHeaders = requestMessage.getHeaders(); - DataBufferFactory bufferFactory = - requestMessageHeaders.get(HandlerMethodReturnValueHandler.DATA_BUFFER_FACTORY_HEADER, - DataBufferFactory.class); - - if (bufferFactory == null) { - bufferFactory = this.rsocketStrategies.dataBufferFactory(); - } - - MimeType mimeType = requestMessageHeaders.get(MessageHeaders.CONTENT_TYPE, MimeType.class); - - return encodeContent(reply, ResolvableType.forInstance(reply), bufferFactory, mimeType); - } - - private Flux encodeContent(Object content, ResolvableType returnValueType, - DataBufferFactory bufferFactory, @Nullable MimeType mimeType) { - - ReactiveAdapter adapter = - this.rsocketStrategies.reactiveAdapterRegistry() - .getAdapter(returnValueType.resolve(), content); - - Publisher publisher; - if (adapter != null) { - publisher = adapter.toPublisher(content); - } - else { - publisher = Flux.just(content); - } - - return Flux.from((Publisher) publisher) - .map((value) -> encodeValue(value, bufferFactory, mimeType)); - } - - private DataBuffer encodeValue(Object element, DataBufferFactory bufferFactory, @Nullable MimeType mimeType) { - ResolvableType elementType = ResolvableType.forInstance(element); - Encoder encoder = this.rsocketStrategies.encoder(elementType, mimeType); - return encoder.encodeValue(element, bufferFactory, elementType, mimeType, null); - } - - @Nullable - @SuppressWarnings("unchecked") - private static AtomicReference getReplyToHeader(Message message) { - Object headerValue = message.getHeaders().get(RSocketPayloadReturnValueHandler.RESPONSE_HEADER); - Assert.state(headerValue == null || headerValue instanceof AtomicReference, "Expected AtomicReference"); - return (AtomicReference) headerValue; - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/inbound/package-info.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/inbound/package-info.java deleted file mode 100644 index a656a45fab4..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes representing inbound RSocket components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.rsocket.inbound; diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/outbound/RSocketOutboundGateway.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/outbound/RSocketOutboundGateway.java deleted file mode 100644 index fe1378275c9..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/outbound/RSocketOutboundGateway.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.outbound; - -import java.util.Arrays; -import java.util.Map; - -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.ResolvableType; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.expression.ValueExpression; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.messaging.Message; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.messaging.rsocket.annotation.support.RSocketRequesterMethodArgumentResolver; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MimeType; - -/** - * An Outbound Messaging Gateway for RSocket requests. - * The request logic is fully based on the {@link RSocketRequester}, which can be obtained from the - * {@link ClientRSocketConnector} on the client side or from the - * {@link RSocketRequesterMethodArgumentResolver#RSOCKET_REQUESTER_HEADER} request message header - * on the server side. - *

- * An RSocket operation is determined by the configured {@link RSocketInteractionModel} or respective SpEL - * expression to be evaluated at runtime against the request message. - * By default, the {@link RSocketInteractionModel#requestResponse} operation is used. - *

- * For a {@link Publisher}-based requests, it must be present in the request message {@code payload}. - * The flattening via upstream {@link org.springframework.integration.channel.FluxMessageChannel} will work, too, - * but this way we will lose a scope of particular request and every {@link Publisher} event - * will be sent in its own plain request. - *

- * If reply is a {@link reactor.core.publisher.Flux}, it is wrapped to the {@link Mono} to retain a request scope. - * The downstream flow is responsible to obtain this {@link reactor.core.publisher.Flux} from a message payload - * and subscribe to it by itself. The {@link Mono} reply from this component is subscribed from the downstream - * {@link org.springframework.integration.channel.FluxMessageChannel} or it is adapted to the - * {@link java.util.concurrent.CompletableFuture} otherwise. - * - * @author Artem Bilan - * - * @since 5.2 - * - * @see RSocketInteractionModel - * @see RSocketRequester - */ -public class RSocketOutboundGateway extends AbstractReplyProducingMessageHandler { - - private final Expression routeExpression; - - private final Object[] routeVars; - - private @Nullable ClientRSocketConnector clientRSocketConnector; - - private Expression interactionModelExpression = new ValueExpression<>(RSocketInteractionModel.requestResponse); - - private @Nullable Expression publisherElementTypeExpression; - - private Expression expectedResponseTypeExpression = new ValueExpression<>(String.class); - - private @Nullable Expression metadataExpression; - - @SuppressWarnings("NullAway.Init") - private EvaluationContext evaluationContext; - - private @Nullable RSocketRequester rsocketRequester; - - /** - * Instantiate based on the provided RSocket endpoint {@code route} - * and optional variables to expand route template. - * @param route the RSocket endpoint route to use. - * @param routeVariables the variables to expand route template. - */ - public RSocketOutboundGateway(String route, Object @Nullable ... routeVariables) { - this(new ValueExpression<>(route), routeVariables); - } - - /** - * Instantiate based on the provided SpEL expression to evaluate an RSocket endpoint {@code route} - * at runtime against a request message. - * If route is a template and variables expansion is required, it is recommended to do that - * in this expression evaluation, for example using some bean with an appropriate logic. - * @param routeExpression the SpEL expression to use. - */ - public RSocketOutboundGateway(Expression routeExpression) { - this(routeExpression, null); - } - - /** - * Instantiate based on the provided SpEL expression to evaluate an RSocket endpoint {@code route} - * at runtime against a request message. - * If route is a template and variables expansion is required, it is recommended to do that - * in this expression evaluation, for example using some bean with an appropriate logic. - * @param routeExpression the SpEL expression to use. - * @param routeVariables the variables to expand route template. - */ - @SuppressWarnings("this-escape") - private RSocketOutboundGateway(Expression routeExpression, Object @Nullable [] routeVariables) { - Assert.notNull(routeExpression, "'routeExpression' must not be null"); - this.routeExpression = routeExpression; - this.routeVars = routeVariables != null - ? Arrays.copyOf(routeVariables, routeVariables.length) - : new Object[0]; - setAsync(true); - setPrimaryExpression(this.routeExpression); - } - - /** - * Configure a {@link ClientRSocketConnector} for client side requests based on the connection - * provided by the {@link ClientRSocketConnector#getRequester()}. - * In case of server side, an {@link RSocketRequester} must be provided in the - * {@link RSocketRequesterMethodArgumentResolver#RSOCKET_REQUESTER_HEADER} header of request message. - * @param clientRSocketConnector the {@link ClientRSocketConnector} to use. - */ - public void setClientRSocketConnector(ClientRSocketConnector clientRSocketConnector) { - Assert.notNull(clientRSocketConnector, "'clientRSocketConnector' must not be null"); - this.clientRSocketConnector = clientRSocketConnector; - } - - /** - * Configure an {@link RSocketInteractionModel} for the RSocket request type. - * @param interactionModel the {@link RSocketInteractionModel} to use. - * @since 5.2.2 - */ - public void setInteractionModel(RSocketInteractionModel interactionModel) { - setInteractionModelExpression(new ValueExpression<>(interactionModel)); - } - - /** - * Configure a SpEL expression to evaluate an {@link RSocketInteractionModel} - * for the RSocket request type at runtime against a request message. - * @param interactionModelExpression the SpEL expression to use. - * @since 5.2.2 - */ - public void setInteractionModelExpression(Expression interactionModelExpression) { - Assert.notNull(interactionModelExpression, "'interactionModelExpression' must not be null"); - this.interactionModelExpression = interactionModelExpression; - } - - /** - * Configure a type for a request {@link Publisher} elements. - * @param publisherElementType the type of the request {@link Publisher} elements. - * @see RSocketRequester.RequestSpec#data(Object, Class) - */ - public void setPublisherElementType(Class publisherElementType) { - setPublisherElementTypeExpression(new ValueExpression<>(publisherElementType)); - - } - - /** - * Configure a SpEL expression to evaluate a request {@link Publisher} elements type at runtime against - * a request message. - * @param publisherElementTypeExpression the expression to evaluate a type for the request - * {@link Publisher} elements. - * @see RSocketRequester.RequestSpec#data - */ - public void setPublisherElementTypeExpression(Expression publisherElementTypeExpression) { - this.publisherElementTypeExpression = publisherElementTypeExpression; - } - - /** - * Specify an response type for the RSocket response. - * @param expectedResponseType The expected type. - * @see #setExpectedResponseTypeExpression(Expression) - * @see RSocketRequester.RequestSpec#retrieveMono - * @see RSocketRequester.RequestSpec#retrieveFlux - */ - public void setExpectedResponseType(Class expectedResponseType) { - setExpectedResponseTypeExpression(new ValueExpression<>(expectedResponseType)); - } - - /** - * Specify an {@link Expression} to determine the type for the RSocket response. - * @param expectedResponseTypeExpression The expected response type expression. - * @see RSocketRequester.RequestSpec#retrieveMono - * @see RSocketRequester.RequestSpec#retrieveFlux - */ - public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExpression) { - this.expectedResponseTypeExpression = expectedResponseTypeExpression; - } - - /** - * Specify a SpEL expression to evaluate a metadata for the RSocket request - * as {@code Map} against a request message. - * @param metadataExpression the expression for metadata. - */ - public void setMetadataExpression(Expression metadataExpression) { - this.metadataExpression = metadataExpression; - } - - @Override - protected void doInit() { - super.doInit(); - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); - if (this.clientRSocketConnector != null) { - this.rsocketRequester = this.clientRSocketConnector.getRequester(); - } - } - - @Override - protected Object handleRequestMessage(Message requestMessage) { - RSocketRequester requester = - requestMessage.getHeaders() - .get(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, RSocketRequester.class); - if (requester == null) { - requester = this.rsocketRequester; - } - - Assert.notNull(requester, - () -> "The 'RSocketRequester' must be configured via 'ClientRSocketConnector' or provided in the '" + - RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER + "' request message headers."); - - return Mono.just(requester) - .map((rSocketRequester) -> createRequestSpec(rSocketRequester, requestMessage)) - .map((requestSpec) -> prepareRetrieveSpec(requestSpec, requestMessage)) - .flatMap((retrieveSpec) -> performRetrieve(retrieveSpec, requestMessage)); - } - - @SuppressWarnings("unchecked") - private RSocketRequester.RequestSpec createRequestSpec(RSocketRequester rsocketRequester, - Message requestMessage) { - - String route = this.routeExpression.getValue(this.evaluationContext, requestMessage, String.class); - Assert.notNull(route, () -> "The 'routeExpression' [" + this.routeExpression + "] must not evaluate to null"); - - RSocketRequester.RequestSpec requestSpec = rsocketRequester.route(route, this.routeVars); - if (this.metadataExpression != null) { - Map metadata = - this.metadataExpression.getValue(this.evaluationContext, requestMessage, Map.class); - if (!CollectionUtils.isEmpty(metadata)) { - requestSpec.metadata((spec) -> metadata.forEach(spec::metadata)); - } - } - - return requestSpec; - } - - private RSocketRequester.RetrieveSpec prepareRetrieveSpec(RSocketRequester.RequestSpec requestSpec, - Message requestMessage) { - - Object payload = requestMessage.getPayload(); - if (payload instanceof Publisher publisher && this.publisherElementTypeExpression != null) { - Object publisherElementType = evaluateExpressionForType(requestMessage, this.publisherElementTypeExpression, - "publisherElementType"); - return prepareRequestSpecForPublisher(requestSpec, publisher, publisherElementType); - } - else { - return requestSpec.data(payload); - } - } - - private RSocketRequester.RetrieveSpec prepareRequestSpecForPublisher(RSocketRequester.RequestSpec requestSpec, - Publisher payload, Object publisherElementType) { - - if (publisherElementType instanceof Class cls) { - return requestSpec.data(payload, cls); - } - else { - return requestSpec.data(payload, (ParameterizedTypeReference) publisherElementType); - } - } - - private Mono performRetrieve(RSocketRequester.RetrieveSpec retrieveSpec, Message requestMessage) { - RSocketInteractionModel interactionModel = evaluateInteractionModel(requestMessage); - Assert.notNull(interactionModel, - () -> "The 'interactionModelExpression' [" + this.interactionModelExpression + - "] must not evaluate to null"); - - if (RSocketInteractionModel.fireAndForget.equals(interactionModel)) { - return retrieveSpec.send(); - } - - Object expectedResponseType = evaluateExpressionForType(requestMessage, this.expectedResponseTypeExpression, - "expectedResponseType"); - - switch (interactionModel) { - case requestResponse: - if (expectedResponseType instanceof Class cls) { - return retrieveSpec.retrieveMono(cls); - } - else { - return retrieveSpec.retrieveMono((ParameterizedTypeReference) expectedResponseType); - } - case requestStream: - case requestChannel: - Flux result; - ResolvableType expectedType; - if (expectedResponseType instanceof Class cls) { - expectedType = ResolvableType.forClass(cls); - result = retrieveSpec.retrieveFlux(cls); - } - else { - expectedType = ResolvableType.forType((ParameterizedTypeReference) expectedResponseType); - result = retrieveSpec.retrieveFlux((ParameterizedTypeReference) expectedResponseType); - } - return isVoid(expectedType) ? result.then() : Mono.just(result); - default: - throw new UnsupportedOperationException("Unsupported interaction model: " + interactionModel); - } - } - - private RSocketInteractionModel evaluateInteractionModel(Message requestMessage) { - Object value = this.interactionModelExpression.getValue(this.evaluationContext, requestMessage); - if (value instanceof RSocketInteractionModel rSocketInteractionModel) { - return rSocketInteractionModel; - } - else if (value instanceof String string) { - return RSocketInteractionModel.valueOf(string); - } - else { - throw new IllegalStateException("The 'interactionModelExpression' [" + - this.interactionModelExpression + - "] must evaluate to 'RSocketInteractionModel' or 'String' type, but not into: '" + value + "'"); - } - } - - private Object evaluateExpressionForType(Message requestMessage, Expression expression, String propertyName) { - Object type = expression.getValue(this.evaluationContext, requestMessage); - Assert.state(type instanceof Class - || type instanceof String - || type instanceof ParameterizedTypeReference, - () -> "The '" + propertyName + "' [" + expression + - "] must evaluate to 'String' (class FQN), 'Class' " + - "or 'ParameterizedTypeReference', not to: " + type); - - if (type instanceof String string) { - try { - return ClassUtils.forName(string, getBeanClassLoader()); - } - catch (ClassNotFoundException e) { - throw new IllegalStateException(e); - } - } - else { - return type; - } - } - - private static boolean isVoid(ResolvableType type) { - return (Void.class.equals(type.resolve()) || void.class.equals(type.resolve())); - } - -} diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/outbound/package-info.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/outbound/package-info.java deleted file mode 100644 index c28c564510e..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes representing outbound RSocket components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.rsocket.outbound; diff --git a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/package-info.java b/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/package-info.java deleted file mode 100644 index 9d34c97f21c..00000000000 --- a/spring-integration-rsocket/src/main/java/org/springframework/integration/rsocket/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides common classes for RSocket components. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.rsocket; diff --git a/spring-integration-rsocket/src/main/resources/META-INF/spring.handlers b/spring-integration-rsocket/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index f17bd16a1c5..00000000000 --- a/spring-integration-rsocket/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/rsocket=org.springframework.integration.rsocket.config.RSocketNamespaceHandler diff --git a/spring-integration-rsocket/src/main/resources/META-INF/spring.schemas b/spring-integration-rsocket/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index b0a97bad503..00000000000 --- a/spring-integration-rsocket/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,4 +0,0 @@ -http\://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket-5.2.xsd=org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd -http\://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket.xsd=org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd -https\://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket-5.2.xsd=org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd -https\://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket.xsd=org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd diff --git a/spring-integration-rsocket/src/main/resources/META-INF/spring.tooling b/spring-integration-rsocket/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index d1a918ab7d4..00000000000 --- a/spring-integration-rsocket/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration rsocket namespace -http\://www.springframework.org/schema/integration/rsocket@name=integration rsocket Namespace -http\://www.springframework.org/schema/integration/rsocket@prefix=int-rsocket -http\://www.springframework.org/schema/integration/rsocket@icon=org/springframework/integration/rsocket/config/spring-integration-rsocket.gif diff --git a/spring-integration-rsocket/src/main/resources/org/springframework/integration/rsocket/config/spring-integration-rsocket.gif b/spring-integration-rsocket/src/main/resources/org/springframework/integration/rsocket/config/spring-integration-rsocket.gif deleted file mode 100644 index 750667e608f..00000000000 Binary files a/spring-integration-rsocket/src/main/resources/org/springframework/integration/rsocket/config/spring-integration-rsocket.gif and /dev/null differ diff --git a/spring-integration-rsocket/src/main/resources/org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd b/spring-integration-rsocket/src/main/resources/org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd deleted file mode 100644 index 58d7599a752..00000000000 --- a/spring-integration-rsocket/src/main/resources/org/springframework/integration/rsocket/config/spring-integration-rsocket.xsd +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - - Defines the configuration elements for Spring Integration's RSocket channel adapters. - - - - - - - - Configures a Messaging Gateway Endpoint for the - 'org.springframework.integration.rsocket.inbound.RSocketInboundGateway' to receive RSocket - requests and produce RSocket responses. - - - - - - - - The comma separated Ant-style path patterns this endpoint is mapped onto. - - - - - - - Comma-separated interaction models. - Determines which types of RSocket frames are allowed with this Endpoint. - - - - - - - - - - - - - - - If a downstream exception is thrown and an error-channel is specified, - the MessagingException will be sent to this channel. Otherwise, any such exception - will be propagated to the calling system. - - - - - - - An 'RSocketStrategies' bean reference for encoding/decoding requests/replies. - - - - - - - - - - - - An optional 'AbstractRSocketConnector' bean reference for endpoint mapping registration. - - - - - - - - - - - - A 'Class' for a request message payload type (plain or `Publisher` element). - - - - - - - Decode incoming Flux as a single unit or each event separately. - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.rsocket.outbound.RSocketOutboundGateway' to send requests - over RSocket connections. - - - - - - - - - - - - - Specifies the order for invocation when this endpoint is connected as a - subscriber to a SubscribableChannel. - - - - - - - A 'route' for the target RSocket endpoint. - Mutually exclusive with 'route-expression'. - - - - - - - A SpEL expression to evaluate a 'route' for target RSocket endpoint at runtime - against request message. - Mutually exclusive with 'route'. - - - - - - - [DEPRECATED in favor of 'interaction-model'] - A 'Command' for the RSocket request type. - Mutually exclusive with 'command-expression'. - - - - - - - - - - An 'RSocketInteractionModel' for the RSocket request type. - Mutually exclusive with 'interaction-model-expression'. - - - - - - - - - - [DEPRECATED in favor of 'interaction-model-expression'] - A SpEL expression to evaluate a 'command' for the RSocket request type at runtime - against request message. - Mutually exclusive with 'command'. - - - - - - - A SpEL expression to evaluate an 'RSocketInteractionModel' - for the RSocket request type at runtime against request message. - Mutually exclusive with 'interaction-model'. - - - - - - - A 'Class' for a request message payload 'Publisher' type. - Mutually exclusive with 'publisher-element-type-expression'. - - - - - - - A SpEL expression to evaluate a 'Class' or 'ParameterizedTypeReference' - for a request message payload 'Publisher' type at runtime - against request message. - Mutually exclusive with 'publisher-element-type'. - - - - - - - A 'Class' for an RSocket response. - Mutually exclusive with 'expected-response-type-expression'. - - - - - - - A SpEL expression to evaluate a 'Class' or 'ParameterizedTypeReference' - for an RSocket response at runtime - against request message. - Mutually exclusive with 'expected-response-type'. - - - - - - - A 'ClientRSocketConnector' for client side requests. - - - - - - - - - - - - A SpEL expression to evaluate a 'Map' representing a metadata - for an RSocket request at runtime against request message. - - - - - - - - - - - - Defines common configuration for gateway adapters. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParserTests-context.xml b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParserTests-context.xml deleted file mode 100644 index b081568cddf..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParserTests-context.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParserTests.java b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParserTests.java deleted file mode 100644 index 1ce2d187ceb..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketInboundGatewayParserTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.integration.rsocket.inbound.RSocketInboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.2 - */ -@SpringJUnitConfig -@DirtiesContext -class RSocketInboundGatewayParserTests { - - @Autowired - private ClientRSocketConnector clientRSocketConnector; - - @Autowired - private RSocketInboundGateway inboundGateway; - - @Test - void testInboundGatewayParser() { - assertThat(TestUtils.getPropertyValue(this.inboundGateway, "rsocketConnector")) - .isSameAs(this.clientRSocketConnector); - assertThat(TestUtils.getPropertyValue(this.inboundGateway, "rsocketStrategies")) - .isSameAs(this.clientRSocketConnector.getRSocketStrategies()); - assertThat(this.inboundGateway.getPath()).containsExactly("testPath"); - assertThat(TestUtils.getPropertyValue(this.inboundGateway, "requestElementType.resolved")) - .isEqualTo(byte[].class); - assertThat(this.inboundGateway.getInteractionModels()) - .containsExactly(RSocketInteractionModel.fireAndForget, RSocketInteractionModel.requestChannel); - assertThat(TestUtils.getPropertyValue(this.inboundGateway, "decodeFluxAsUnit", Boolean.class)).isTrue(); - } - -} diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParserTests-context.xml b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParserTests-context.xml deleted file mode 100644 index fcf7e51b7cb..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParserTests-context.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParserTests.java b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParserTests.java deleted file mode 100644 index 63c1d204420..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/config/RSocketOutboundGatewayParserTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.config; - -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.expression.Expression; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.MimeType; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.2 - */ -@SpringJUnitConfig -@DirtiesContext -class RSocketOutboundGatewayParserTests { - - @Autowired - private ClientRSocketConnector clientRSocketConnector; - - @Autowired - private RSocketOutboundGateway outboundGateway; - - @Test - void testOutboundGatewayParser() { - assertThat(TestUtils.getPropertyValue(this.outboundGateway, "clientRSocketConnector")) - .isSameAs(this.clientRSocketConnector); - assertThat(TestUtils.getPropertyValue(this.outboundGateway, "interactionModelExpression.literalValue")) - .isEqualTo("fireAndForget"); - assertThat(TestUtils.getPropertyValue(this.outboundGateway, "routeExpression.expression")) - .isEqualTo("'testRoute'"); - assertThat(TestUtils.getPropertyValue(this.outboundGateway, "publisherElementTypeExpression.literalValue")) - .isEqualTo("byte[]"); - assertThat(TestUtils.getPropertyValue(this.outboundGateway, "expectedResponseTypeExpression.literalValue")) - .isEqualTo("java.util.Date"); - Expression metadataExpression = - TestUtils.getPropertyValue(this.outboundGateway, "metadataExpression", Expression.class); - assertThat(metadataExpression.getValue()) - .isEqualTo(Collections.singletonMap("metadata", new MimeType("*"))); - } - -} diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/dsl/RSocketDslTests.java b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/dsl/RSocketDslTests.java deleted file mode 100644 index 1dd87f364a8..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/dsl/RSocketDslTests.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.dsl; - -import java.time.Duration; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.context.IntegrationFlowContext; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.integration.rsocket.ServerRSocketConnector; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @since 5.2 - */ -@SpringJUnitConfig -@DirtiesContext -public class RSocketDslTests { - - @Autowired - @Qualifier("rsocketUpperCaseRequestFlow.gateway") - private Function> rsocketUpperCaseFlowFunction; - - @Test - void testRsocketUpperCaseFlows() { - Flux result = this.rsocketUpperCaseFlowFunction.apply(Flux.just("a", "b", "c")); - - StepVerifier.create(result) - .expectNext("A", "B", "C") - .verifyComplete(); - } - - @Test - void testRsocketUpperCaseWholeFlows() { - Message> testMessage = - MessageBuilder.withPayload(Flux.just("a", "b", "c", "\n")) - .setHeader("route", "/uppercaseWhole") - .build(); - Flux result = this.rsocketUpperCaseFlowFunction.apply(testMessage); - - StepVerifier.create(result) - .expectNext("ABC") - .verifyComplete(); - } - - @Autowired - IntegrationFlowContext integrationFlowContext; - - @Autowired - ClientRSocketConnector clientRSocketConnector; - - @Test - void testNoBlockingForReactiveThreads() { - IntegrationFlow flow = - f -> f - .handle(RSockets.outboundGateway("/lowercase") - .clientRSocketConnector(this.clientRSocketConnector), - endpoint -> endpoint.customizeMonoReply((message, mono) -> - mono.publishOn(Schedulers.boundedElastic()))) - .transform("{ firstResult: payload }") - .enrich(e -> e - .requestPayloadExpression("payload.firstResult") - .requestSubFlow( - sf -> sf - .handle(RSockets.outboundGateway("/lowercase") - .clientRSocketConnector(this.clientRSocketConnector))) - .propertyExpression("secondResult", "payload")) - .transform("payload.values().toString()"); - - IntegrationFlowContext.IntegrationFlowRegistration flowRegistration = - this.integrationFlowContext.registration(flow).register(); - - String result = flowRegistration.getMessagingTemplate().convertSendAndReceive("TEST", String.class); - - assertThat(result).isEqualTo("[test, test]"); - - flowRegistration.destroy(); - } - - @Configuration - @EnableIntegration - public static class TestConfiguration { - - @Bean - public ServerRSocketConnector serverRSocketConnector() { - return new ServerRSocketConnector("localhost", 0); - } - - @Bean - public ClientRSocketConnector clientRSocketConnector(ServerRSocketConnector serverRSocketConnector) { - int port = serverRSocketConnector.getBoundPort().block(); - ClientRSocketConnector clientRSocketConnector = new ClientRSocketConnector("localhost", port); - clientRSocketConnector.setAutoStartup(false); - return clientRSocketConnector; - } - - @Bean - public IntegrationFlow rsocketUpperCaseRequestFlow(ClientRSocketConnector clientRSocketConnector) { - return IntegrationFlow - .from(Function.class) - .handle(RSockets.outboundGateway(message -> - message.getHeaders().getOrDefault("route", "/uppercase")) - .interactionModel((message) -> RSocketInteractionModel.requestChannel) - .expectedResponseType("T(java.lang.String)") - .clientRSocketConnector(clientRSocketConnector), - e -> e.customizeMonoReply( - (message, mono) -> - mono.timeout(Duration.ofMillis(100)) - .retry())) - .get(); - } - - @Bean - public IntegrationFlow rsocketUpperCaseFlow() { - return IntegrationFlow - .from(RSockets.inboundGateway("/uppercase") - .interactionModels(RSocketInteractionModel.requestChannel)) - ., Flux>transform((flux) -> flux.map(String::toUpperCase)) - .get(); - } - - @Bean - public IntegrationFlow rsocketUpperCaseWholeFlow() { - return IntegrationFlow - .from(RSockets.inboundGateway("/uppercaseWhole") - .interactionModels(RSocketInteractionModel.requestChannel) - .decodeFluxAsUnit(true)) - ., Flux>transform((flux) -> flux.map(String::toUpperCase)) - .get(); - } - - @Bean - public IntegrationFlow rsocketLowerCaseFlow() { - return IntegrationFlow - .from(RSockets.inboundGateway("/lowercase")) - ., Flux>transform((flux) -> flux.map(String::toLowerCase)) - .get(); - } - - } - -} diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/inbound/RSocketInboundGatewayIntegrationTests.java b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/inbound/RSocketInboundGatewayIntegrationTests.java deleted file mode 100644 index 62585f10fc2..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/inbound/RSocketInboundGatewayIntegrationTests.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.inbound; - -import java.time.Duration; - -import io.rsocket.core.RSocketServer; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.transport.netty.server.CloseableChannel; -import io.rsocket.transport.netty.server.TcpServerTransport; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.EventListener; -import org.springframework.integration.annotation.Transformer; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.RSocketConnectedEvent; -import org.springframework.integration.rsocket.ServerRSocketConnector; -import org.springframework.integration.rsocket.ServerRSocketMessageHandler; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.2 - */ -@SpringJUnitConfig(RSocketInboundGatewayIntegrationTests.ClientConfig.class) -@DirtiesContext -public class RSocketInboundGatewayIntegrationTests { - - private static AnnotationConfigApplicationContext serverContext; - - private static ServerConfig serverConfig; - - private static PollableChannel serverFireAndForgetChannelChannel; - - @Autowired - private ClientRSocketConnector clientRSocketConnector; - - @Autowired - private PollableChannel fireAndForgetChannelChannel; - - private RSocketRequester serverRsocketRequester; - - private RSocketRequester clientRsocketRequester; - - @BeforeAll - static void setup() { - serverContext = new AnnotationConfigApplicationContext(ServerConfig.class); - serverConfig = serverContext.getBean(ServerConfig.class); - serverFireAndForgetChannelChannel = serverContext.getBean("fireAndForgetChannelChannel", PollableChannel.class); - } - - @AfterAll - static void tearDown() { - serverContext.close(); - } - - @BeforeEach - void setupTest(TestInfo testInfo) { - if (testInfo.getDisplayName().startsWith("server")) { - this.serverRsocketRequester = serverConfig.clientRequester.asMono().block(Duration.ofSeconds(10)); - } - else { - this.clientRsocketRequester = this.clientRSocketConnector.getRequester(); - } - } - - @Test - void clientFireAndForget() { - fireAndForget(serverFireAndForgetChannelChannel, this.clientRsocketRequester); - } - - @Test - void serverFireAndForget() { - fireAndForget(this.fireAndForgetChannelChannel, this.serverRsocketRequester); - } - - private void fireAndForget(PollableChannel inputChannel, RSocketRequester rsocketRequester) { - rsocketRequester.route("receive") - .data("Hello") - .send() - .subscribe(); - - Message receive = inputChannel.receive(10_000); - assertThat(receive) - .isNotNull() - .extracting(Message::getPayload) - .isEqualTo("Hello"); - } - - @Test - void clientEcho() { - echo(this.clientRsocketRequester); - } - - @Test - void serverEcho() { - echo(this.serverRsocketRequester); - } - - private void echo(RSocketRequester rsocketRequester) { - Flux result = - Flux.range(1, 3) - .concatMap(i -> - rsocketRequester.route("echo") - .data("hello " + i) - .retrieveMono(String.class)); - - StepVerifier.create(result) - .expectNext("HELLO 1", "HELLO 2", "HELLO 3") - .expectComplete() - .verify(Duration.ofSeconds(10)); - } - - private abstract static class CommonConfig { - - @Bean - public PollableChannel fireAndForgetChannelChannel() { - return new QueueChannel(); - } - - @Bean - public RSocketInboundGateway rsocketInboundGatewayFireAndForget() { - RSocketInboundGateway rsocketInboundGateway = new RSocketInboundGateway("receive"); - rsocketInboundGateway.setRequestChannel(fireAndForgetChannelChannel()); - return rsocketInboundGateway; - } - - @Bean - public RSocketInboundGateway rsocketInboundGatewayRequestReply() { - RSocketInboundGateway rsocketInboundGateway = new RSocketInboundGateway("echo"); - rsocketInboundGateway.setRequestChannelName("requestReplyChannel"); - return rsocketInboundGateway; - } - - @Transformer(inputChannel = "requestReplyChannel") - public Mono echoTransformation(Flux payload) { - return payload.next().map(String::toUpperCase); - } - - } - - @Configuration - @EnableIntegration - static class ServerConfig extends CommonConfig { - - final Sinks.One clientRequester = Sinks.one(); - - @Bean - public CloseableChannel rsocketServer() { - return RSocketServer.create() - .payloadDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(serverRSocketMessageHandler().responder()) - .bind(TcpServerTransport.create("localhost", 0)) - .block(); - } - - @Bean - public ServerRSocketMessageHandler serverRSocketMessageHandler() { - return new ServerRSocketMessageHandler(true); - } - - @Bean - public ServerRSocketConnector serverRSocketConnector(ServerRSocketMessageHandler serverRSocketMessageHandler) { - return new ServerRSocketConnector(serverRSocketMessageHandler); - } - - @EventListener - public void onApplicationEvent(RSocketConnectedEvent event) { - this.clientRequester.tryEmitValue(event.getRequester()); - } - - } - - @Configuration - @EnableIntegration - public static class ClientConfig extends CommonConfig { - - @Bean - public ClientRSocketConnector clientRSocketConnector() { - ClientRSocketConnector clientRSocketConnector = - new ClientRSocketConnector("localhost", serverConfig.rsocketServer().address().getPort()); - clientRSocketConnector.setSetupRoute("clientConnect/{user}"); - clientRSocketConnector.setSetupRouteVariables("myUser"); - return clientRSocketConnector; - } - - } - -} diff --git a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/outbound/RSocketOutboundGatewayIntegrationTests.java b/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/outbound/RSocketOutboundGatewayIntegrationTests.java deleted file mode 100644 index fa8172eb59c..00000000000 --- a/spring-integration-rsocket/src/test/java/org/springframework/integration/rsocket/outbound/RSocketOutboundGatewayIntegrationTests.java +++ /dev/null @@ -1,630 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.rsocket.outbound; - -import java.time.Duration; - -import io.rsocket.RSocket; -import io.rsocket.core.RSocketServer; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.transport.netty.server.CloseableChannel; -import io.rsocket.transport.netty.server.TcpServerTransport; -import org.jspecify.annotations.Nullable; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.channel.FluxMessageChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.dsl.MessageChannels; -import org.springframework.integration.expression.FunctionExpression; -import org.springframework.integration.rsocket.ClientRSocketConnector; -import org.springframework.integration.rsocket.RSocketInteractionModel; -import org.springframework.integration.rsocket.ServerRSocketMessageHandler; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandlingException; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.handler.annotation.MessageExceptionHandler; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.rsocket.RSocketRequester; -import org.springframework.messaging.rsocket.RSocketStrategies; -import org.springframework.messaging.rsocket.annotation.ConnectMapping; -import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; -import org.springframework.messaging.rsocket.annotation.support.RSocketRequesterMethodArgumentResolver; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.stereotype.Controller; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.2 - */ -@SpringJUnitConfig(RSocketOutboundGatewayIntegrationTests.ClientConfig.class) -@DirtiesContext -public class RSocketOutboundGatewayIntegrationTests { - - private static final String ROUTE_HEADER = "rsocket_route"; - - private static final String INTERACTION_MODEL_HEADER = "interaction_model"; - - private static AnnotationConfigApplicationContext serverContext; - - private static CloseableChannel server; - - private static FluxMessageChannel serverInputChannel; - - private static FluxMessageChannel serverResultChannel; - - private static PollableChannel serverErrorChannel; - - private static TestController serverController; - - @Autowired - private FluxMessageChannel inputChannel; - - @Autowired - private FluxMessageChannel resultChannel; - - @Autowired - private PollableChannel errorChannel; - - @Autowired - private TestController clientController; - - @Autowired - RSocketOutboundGateway clientRsocketOutboundGateway; - - private RSocketRequester serverRsocketRequester; - - @BeforeAll - static void setup() { - serverContext = new AnnotationConfigApplicationContext(ServerConfig.class); - server = RSocketServer.create() - .payloadDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(serverContext.getBean(RSocketMessageHandler.class).responder()) - .bind(TcpServerTransport.create("localhost", 0)) - .block(); - - serverController = serverContext.getBean(TestController.class); - serverInputChannel = serverContext.getBean("inputChannel", FluxMessageChannel.class); - serverResultChannel = serverContext.getBean("resultChannel", FluxMessageChannel.class); - serverErrorChannel = serverContext.getBean("errorChannel", PollableChannel.class); - } - - @AfterAll - static void tearDown() { - serverContext.close(); - server.dispose(); - } - - @BeforeEach - void setupTest(TestInfo testInfo) { - if (testInfo.getDisplayName().startsWith("server")) { - this.serverRsocketRequester = serverController.clientRequester.asMono().block(Duration.ofSeconds(10)); - } - } - - @Test - void clientFireAndForget() { - fireAndForget(this.inputChannel, this.resultChannel, serverController, null); - } - - @Test - void serverFireAndForget() { - fireAndForget(serverInputChannel, serverResultChannel, this.clientController, this.serverRsocketRequester); - } - - private void fireAndForget(MessageChannel inputChannel, FluxMessageChannel resultChannel, - TestController controller, RSocketRequester rsocketRequester) { - - Disposable disposable = Flux.from(resultChannel).subscribe(); - inputChannel.send( - MessageBuilder.withPayload("Hello") - .setHeader(ROUTE_HEADER, "receive") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.fireAndForget) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - StepVerifier.create(controller.fireForgetPayloads.asFlux()) - .expectNext("Hello") - .thenCancel() - .verify(Duration.ofSeconds(10)); - - disposable.dispose(); - } - - @Test - void clientEcho() { - echo(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverEcho() { - echo(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void echo(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - StepVerifier verifier = - StepVerifier.create( - Flux.from(resultChannel) - .map(Message::getPayload) - .cast(String.class)) - .expectNext("Hello") - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("Hello") - .setHeader(ROUTE_HEADER, "echo") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientEchoAsync() { - echoAsync(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverEchoAsync() { - echoAsync(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void echoAsync(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - StepVerifier verifier = - StepVerifier.create( - Flux.from(resultChannel) - .map(Message::getPayload) - .cast(String.class)) - .expectNext("Hello async") - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("Hello") - .setHeader(ROUTE_HEADER, "echo-async") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientEchoStream() { - echoStream(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverEchoStream() { - echoStream(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void echoStream(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - @SuppressWarnings("unchecked") - StepVerifier verifier = - StepVerifier.create( - Flux.from(resultChannel) - .next() - .map(Message::getPayload) - .flatMapMany((payload) -> (Flux) payload)) - .expectNext("Hello 0").expectNextCount(6).expectNext("Hello 7") - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("Hello") - .setHeader(ROUTE_HEADER, "echo-stream") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestStream) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientEchoChannel() { - echoChannel(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverEchoChannel() { - echoChannel(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void echoChannel(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - @SuppressWarnings("unchecked") - StepVerifier verifier = - StepVerifier.create( - Flux.from(resultChannel) - .next() - .map(Message::getPayload) - .flatMapMany((payload) -> (Flux) payload)) - .expectNext("Hello 1 async").expectNextCount(8).expectNext("Hello 10 async") - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload(Flux.range(1, 10).map(i -> "Hello " + i)) - .setHeader(ROUTE_HEADER, "echo-channel") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestChannel) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientVoidReturnValue() { - voidReturnValue(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverVoidReturnValue() { - voidReturnValue(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void voidReturnValue(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - StepVerifier verifier = - StepVerifier.create(resultChannel) - .expectSubscription() - .expectNoEvent(Duration.ofMillis(100)) - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("Hello") - .setHeader(ROUTE_HEADER, "void-return-value") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientVoidReturnValueFromExceptionHandler() { - voidReturnValueFromExceptionHandler(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverVoidReturnValueFromExceptionHandler() { - voidReturnValueFromExceptionHandler(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void voidReturnValueFromExceptionHandler(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - StepVerifier verifier = - StepVerifier.create(resultChannel) - .expectSubscription() - .expectNoEvent(Duration.ofMillis(100)) - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("bad") - .setHeader(ROUTE_HEADER, "void-return-value") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientHandleWithThrownException() { - handleWithThrownException(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverHandleWithThrownException() { - handleWithThrownException(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void handleWithThrownException(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - StepVerifier verifier = - StepVerifier.create( - Flux.from(resultChannel) - .map(Message::getPayload) - .cast(String.class)) - .expectNext("Invalid input error handled") - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("a") - .setHeader(ROUTE_HEADER, "thrown-exception") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientHandleWithErrorSignal() { - handleWithErrorSignal(this.inputChannel, this.resultChannel, null); - } - - @Test - void serverHandleWithErrorSignal() { - handleWithErrorSignal(serverInputChannel, serverResultChannel, this.serverRsocketRequester); - } - - private void handleWithErrorSignal(MessageChannel inputChannel, FluxMessageChannel resultChannel, - RSocketRequester rsocketRequester) { - - StepVerifier verifier = - StepVerifier.create( - Flux.from(resultChannel) - .map(Message::getPayload) - .cast(String.class)) - .expectNext("Invalid input error handled") - .thenCancel() - .verifyLater(); - - inputChannel.send( - MessageBuilder.withPayload("a") - .setHeader(ROUTE_HEADER, "error-signal") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - verifier.verify(Duration.ofSeconds(10)); - } - - @Test - void clientNoMatchingRoute() { - noMatchingRoute(this.inputChannel, this.resultChannel, this.errorChannel, null); - } - - @Test - void serverNoMatchingRoute() { - noMatchingRoute(serverInputChannel, serverResultChannel, serverErrorChannel, this.serverRsocketRequester); - } - - @Test - void voidRequestChannel() { - Disposable disposable = Flux.from(resultChannel).subscribe(); - this.clientRsocketOutboundGateway.setExpectedResponseType(void.class); - Flux testData = Flux.range(1, 10).map(i -> "Hello " + i); - this.inputChannel.send( - MessageBuilder.withPayload(testData) - .setHeader(ROUTE_HEADER, "void-channel") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestChannel) - .build()); - - StepVerifier.create(serverController.voidChannelPayloads.asFlux()) - .expectNext(testData.toStream().toArray(String[]::new)) - .thenCancel() - .verify(Duration.ofSeconds(10)); - - disposable.dispose(); - this.clientRsocketOutboundGateway.setExpectedResponseType(String.class); - } - - private void noMatchingRoute(MessageChannel inputChannel, FluxMessageChannel resultChannel, - PollableChannel errorChannel, RSocketRequester rsocketRequester) { - - Disposable disposable = Flux.from(resultChannel).subscribe(); - inputChannel.send( - MessageBuilder.withPayload("anything") - .setHeader(ROUTE_HEADER, "invalid") - .setHeader(INTERACTION_MODEL_HEADER, RSocketInteractionModel.requestResponse) - .setHeader(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, rsocketRequester) - .build()); - - Message errorMessage = errorChannel.receive(10_000); - - assertThat(errorMessage).isNotNull() - .isInstanceOf(ErrorMessage.class) - .extracting(Message::getPayload) - .isInstanceOf(MessageHandlingException.class) - .satisfies((ex) -> assertThat((Exception) ex) - .hasStackTraceContaining( - "ApplicationErrorException (0x201): No handler for destination 'invalid'")); - - disposable.dispose(); - } - - private abstract static class CommonConfig { - - @Bean - public TestController controller() { - return new TestController(); - } - - @Bean - public RSocketOutboundGateway rsocketOutboundGateway() { - RSocketOutboundGateway rsocketOutboundGateway = - new RSocketOutboundGateway( - new FunctionExpression>((m) -> - m.getHeaders().get(ROUTE_HEADER))); - rsocketOutboundGateway.setInteractionModelExpression( - new FunctionExpression>((m) -> m.getHeaders().get(INTERACTION_MODEL_HEADER))); - return rsocketOutboundGateway; - } - - @Bean - public IntegrationFlow rsocketOutboundFlow() { - return IntegrationFlow.from(MessageChannels.flux("inputChannel")) - .handle(rsocketOutboundGateway()) - .channel(c -> c.flux("resultChannel")) - .get(); - } - - @Bean - public PollableChannel errorChannel() { - return new QueueChannel(); - } - - } - - @Configuration - @EnableIntegration - public static class ClientConfig extends CommonConfig { - - @Bean(destroyMethod = "dispose") - @Nullable - public RSocket rsocketForServerRequests() { - - return RSocketRequester.builder() - .setupRoute("clientConnect") - .rsocketConnector(connector -> - connector.acceptor( - RSocketMessageHandler.responder(RSocketStrategies.create(), controller()))) - .tcp("localhost", server.address().getPort()) - .rsocketClient() - .source() - .block(); - } - - @Bean - public ClientRSocketConnector clientRSocketConnector() { - return new ClientRSocketConnector("localhost", server.address().getPort()); - } - - @Override - @Bean - public RSocketOutboundGateway rsocketOutboundGateway() { - RSocketOutboundGateway rsocketOutboundGateway = super.rsocketOutboundGateway(); - rsocketOutboundGateway.setClientRSocketConnector(clientRSocketConnector()); - return rsocketOutboundGateway; - } - - } - - @Configuration - @EnableIntegration - static class ServerConfig extends CommonConfig { - - @Bean - public RSocketMessageHandler messageHandler() { - return new ServerRSocketMessageHandler(true); - } - - } - - @Controller - static class TestController { - - final Sinks.Many fireForgetPayloads = Sinks.many().replay().all(); - - final Sinks.Many voidChannelPayloads = Sinks.many().replay().all(); - - final Sinks.One clientRequester = Sinks.one(); - - @MessageMapping("receive") - void receive(String payload) { - this.fireForgetPayloads.tryEmitNext(payload); - } - - @MessageMapping("echo") - String echo(String payload) { - return payload; - } - - @MessageMapping("echo-async") - Mono echoAsync(String payload) { - return Mono.delay(Duration.ofMillis(10)).map(aLong -> payload + " async"); - } - - @MessageMapping("echo-stream") - Flux echoStream(String payload) { - return Flux.interval(Duration.ofMillis(10)).map(aLong -> payload + " " + aLong); - } - - @MessageMapping("echo-channel") - Flux echoChannel(Flux payloads) { - return payloads.delayElements(Duration.ofMillis(10)).map(payload -> payload + " async"); - } - - @MessageMapping("void-channel") - Mono voidChannel(Flux payloads) { - return payloads.map(voidChannelPayloads::tryEmitNext).then(); - } - - @MessageMapping("thrown-exception") - Mono handleAndThrow(String payload) { - throw new IllegalArgumentException("Invalid input error"); - } - - @MessageMapping("error-signal") - Mono handleAndReturnError(String payload) { - return Mono.error(new IllegalArgumentException("Invalid input error")); - } - - @MessageMapping("void-return-value") - Mono voidReturnValue(String payload) { - return !payload.equals("bad") ? - Mono.delay(Duration.ofMillis(10)).then(Mono.empty()) : - Mono.error(new IllegalStateException("bad")); - } - - @MessageExceptionHandler - Mono handleException(IllegalArgumentException ex) { - return Mono.delay(Duration.ofMillis(10)).map(aLong -> ex.getMessage() + " handled"); - } - - @MessageExceptionHandler - Mono handleExceptionWithVoidReturnValue(IllegalStateException ex) { - return Mono.delay(Duration.ofMillis(10)).then(Mono.empty()); - } - - @ConnectMapping("clientConnect") - void clientConnect(RSocketRequester requester) { - this.clientRequester.tryEmitValue(requester); - } - - } - -} diff --git a/spring-integration-rsocket/src/test/resources/log4j2-test.xml b/spring-integration-rsocket/src/test/resources/log4j2-test.xml deleted file mode 100644 index d6397df41cc..00000000000 --- a/spring-integration-rsocket/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java deleted file mode 100644 index 1cab53ab318..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.messaging.Message; -import org.springframework.scripting.ScriptSource; -import org.springframework.util.Assert; - -/** - * Base {@link MessageProcessor} for scripting implementations to extend. - * - * @param the payload type. - * - * @author Mark Fisher - * @author Stefan Reuter - * @author Artem Bilan - * - * @since 2.0 - */ -public abstract class AbstractScriptExecutingMessageProcessor - implements MessageProcessor, BeanClassLoaderAware, BeanFactoryAware { - - private final ScriptVariableGenerator scriptVariableGenerator; - - @SuppressWarnings("NullAway.Init") - private ClassLoader beanClassLoader; - - @SuppressWarnings("NullAway.Init") - private BeanFactory beanFactory; - - protected AbstractScriptExecutingMessageProcessor() { - this(new DefaultScriptVariableGenerator()); - } - - protected AbstractScriptExecutingMessageProcessor(ScriptVariableGenerator scriptVariableGenerator) { - Assert.notNull(scriptVariableGenerator, "scriptVariableGenerator must not be null"); - this.scriptVariableGenerator = scriptVariableGenerator; - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - protected ScriptVariableGenerator getScriptVariableGenerator() { - return this.scriptVariableGenerator; - } - - protected ClassLoader getBeanClassLoader() { - return this.beanClassLoader; - } - - protected BeanFactory getBeanFactory() { - return this.beanFactory; - } - - /** - * Execute the script and return the result. - */ - @Override - public final @Nullable T processMessage(Message message) { - ScriptSource source = getScriptSource(message); - Map variables = this.scriptVariableGenerator.generateScriptVariables(message); - return executeScript(source, variables); - } - - /** - * Subclasses must implement this method to create a script source, - * optionally using the message to locate or create the script. - * @param message the message being processed - * @return a ScriptSource to use to create a script - */ - protected abstract ScriptSource getScriptSource(Message message); - - /** - * Subclasses must implement this method. In doing so, the execution context - * for the script should be populated with the provided script variables. - * @param scriptSource The script source. - * @param variables The variables. - * @return The result of the execution. - */ - protected abstract @Nullable T executeScript(ScriptSource scriptSource, Map variables); - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/DefaultScriptVariableGenerator.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/DefaultScriptVariableGenerator.java deleted file mode 100644 index 43a4fb4876d..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/DefaultScriptVariableGenerator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.messaging.Message; -import org.springframework.util.CollectionUtils; - -/** - * A default {@link ScriptVariableGenerator} implementation; used by script processors. - * The result of {@link #generateScriptVariables(Message)} is a {@link Map} of any provided {@code variables} - * plus {@code payload} and {@code headers} from the {@code Message} argument. - * - * @author Oleg Zhurakousky - * @author Mark Fisher - * @since 2.0.2 - */ -public class DefaultScriptVariableGenerator implements ScriptVariableGenerator { - - private final Map variableMap; - - public DefaultScriptVariableGenerator() { - this.variableMap = Collections.emptyMap(); - } - - public DefaultScriptVariableGenerator(Map variableMap) { - this.variableMap = variableMap; - } - - public Map generateScriptVariables(Message message) { - Map scriptVariables = new HashMap<>(); - // Add Message content - if (message != null) { - scriptVariables.put("payload", message.getPayload()); - scriptVariables.put("headers", message.getHeaders()); - } - // Add contents of 'variableMap' - if (!CollectionUtils.isEmpty(this.variableMap)) { - for (Map.Entry entry : this.variableMap.entrySet()) { - scriptVariables.put(entry.getKey(), entry.getValue()); - } - } - return scriptVariables; - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java deleted file mode 100644 index fa46d93c906..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Value; -import org.jspecify.annotations.Nullable; - -import org.springframework.scripting.ScriptSource; -import org.springframework.util.Assert; - -/** - * GraalVM Polyglot {@link ScriptExecutor} implementation. - * - * @author Artem Bilan - * - * @since 6.0 - */ -public class PolyglotScriptExecutor implements ScriptExecutor { - - private final String language; - - private final Context.Builder contextBuilder; - - /** - * Construct an executor based on the provided language id. - * @param language the supported by GraalVM language id. - */ - public PolyglotScriptExecutor(String language) { - this(language, Context.newBuilder().allowAllAccess(true)); - } - - /** - * Construct an executor based on the provided language id. - * @param language the supported by GraalVM language id. - */ - public PolyglotScriptExecutor(String language, Context.Builder contextBuilder) { - Assert.hasText(language, "'language' must not be empty"); - Assert.notNull(contextBuilder, "'contextBuilder' must not be null"); - this.contextBuilder = contextBuilder; - this.language = language; - try (Context context = this.contextBuilder.build()) { - context.initialize(language); - } - } - - @Override - public @Nullable Object executeScript(ScriptSource scriptSource, @Nullable Map variables) { - try (Context context = this.contextBuilder.build()) { - if (variables != null) { - Value bindings = context.getBindings(this.language); - variables.forEach(bindings::putMember); - } - String scriptAsString = scriptSource.getScriptAsString(); - Object result = context.eval(this.language, scriptAsString).as(Object.class); - // We have to copy all the expected PolyglotWrapper instances before context is closed. - if (result instanceof Map map) { - String returnVariable = parseReturnVariable(scriptAsString); - result = map.get(returnVariable); - } - if (result instanceof List list) { - result = new ArrayList<>(list); - } - return result; - } - catch (Exception ex) { - throw new ScriptingException(ex.getMessage(), ex); - } - } - - private static String parseReturnVariable(String script) { - String[] lines = script.trim().split("\n"); - String lastLine = lines[lines.length - 1]; - String[] tokens = lastLine.split("="); - return tokens[0].trim(); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java deleted file mode 100644 index 7f01988b937..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicLong; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.io.Resource; -import org.springframework.scripting.ScriptSource; -import org.springframework.scripting.support.ResourceScriptSource; - -/** - * A {@link ScriptSource} implementation, which caches a script string and refreshes it from the - * target file (if modified) according the provided {@link #refreshDelay}. - * - * @author Dave Syer - * @author Oleg Zhurakousky - * @author Artem Bilan - * - * @since 2.0 - */ -public class RefreshableResourceScriptSource implements ScriptSource { - - private final long refreshDelay; - - private final ResourceScriptSource source; - - private final AtomicLong lastModifiedChecked = new AtomicLong(System.currentTimeMillis()); - - @SuppressWarnings("NullAway.Init") - private volatile String script; - - public RefreshableResourceScriptSource(Resource resource, long refreshDelay) { - this.refreshDelay = refreshDelay; - this.source = new ResourceScriptSource(resource); - try { - this.script = this.source.getScriptAsString(); - } - catch (IOException e) { - this.lastModifiedChecked.set(0); - } - } - - public String getScriptAsString() throws IOException { - if (this.script == null || this.isModified()) { - this.lastModifiedChecked.set(System.currentTimeMillis()); - this.script = this.source.getScriptAsString(); - } - return this.script; - } - - public @Nullable String suggestedClassName() { - return this.source.getResource().getFilename(); - } - - public boolean isModified() { - return this.refreshDelay >= 0 && - (System.currentTimeMillis() - this.lastModifiedChecked.get()) > this.refreshDelay && - this.source.isModified(); - } - - @Override - public String toString() { - return this.source.toString(); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptExecutingMessageSource.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptExecutingMessageSource.java deleted file mode 100644 index e7efced58ab..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptExecutingMessageSource.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.endpoint.AbstractMessageSource; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; - -/** - * The {@link org.springframework.integration.core.MessageSource} strategy implementation - * to produce a {@link org.springframework.messaging.Message} from underlying - * {@linkplain #scriptMessageProcessor} for polling endpoints. - * - * @author Artem Bilan - * @author Gary Russell - * @since 3.0 - */ -public class ScriptExecutingMessageSource extends AbstractMessageSource { - - private static final Message EMPTY_MESSAGE = MessageBuilder.withPayload(new byte[0]).build(); - - private final AbstractScriptExecutingMessageProcessor scriptMessageProcessor; - - public ScriptExecutingMessageSource(AbstractScriptExecutingMessageProcessor scriptMessageProcessor) { - this.scriptMessageProcessor = scriptMessageProcessor; - } - - @Override - public String getComponentType() { - return "inbound-channel-adapter"; - } - - @Override - protected @Nullable Object doReceive() { - return this.scriptMessageProcessor.processMessage(EMPTY_MESSAGE); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptExecutor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptExecutor.java deleted file mode 100644 index dc09f7af33a..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptExecutor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.scripting.ScriptSource; - -/** - * A script evaluation abstraction against {@link ScriptSource} and optional binding {@code variables}. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -@FunctionalInterface -public interface ScriptExecutor { - - /** - * Execute a script from the provided {@link ScriptSource} with an optional binding {@code variables}. - * @param scriptSource The script source. - * @param variables The variables. - * @return The result of the execution. - */ - @Nullable - Object executeScript(ScriptSource scriptSource, @Nullable Map variables); - - /** - * Execute a script from the provided {@link ScriptSource} - * @param scriptSource The script source. - * @return The result of the execution. - */ - default @Nullable Object executeScript(ScriptSource scriptSource) { - return executeScript(scriptSource, null); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptVariableGenerator.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptVariableGenerator.java deleted file mode 100644 index 8299b6bc387..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptVariableGenerator.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import java.util.Map; - -import org.springframework.messaging.Message; - -/** - * Strategy interface to provide a {@link Map} of variables to the script execution context. - * Variables may be extracted from the {@link Message} argument. - * - * @author Oleg Zhurakousky - * @since 2.0.2 - */ -@FunctionalInterface -public interface ScriptVariableGenerator { - - Map generateScriptVariables(Message message); - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptingException.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptingException.java deleted file mode 100644 index 2312ed16c51..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/ScriptingException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting; - -import org.jspecify.annotations.Nullable; - -import org.springframework.messaging.MessagingException; - -/** - * @author David Turanski - * @since 2.1 - */ -@SuppressWarnings("serial") - -public class ScriptingException extends MessagingException { - - public ScriptingException(String description) { - super(description); - } - - public ScriptingException(@Nullable String description, Throwable cause) { - super(description, cause); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/AbstractScriptParser.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/AbstractScriptParser.java deleted file mode 100644 index f61245437f0..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/AbstractScriptParser.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config; - -import java.util.List; - -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.scripting.DefaultScriptVariableGenerator; -import org.springframework.integration.scripting.RefreshableResourceScriptSource; -import org.springframework.scripting.support.StaticScriptSource; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; - -/** - * @author David Turanski - * @author Artem Bilan - * @author Ngoc Nhan - * - */ -public abstract class AbstractScriptParser extends AbstractSingleBeanDefinitionParser { - - protected static final String LOCATION_ATTRIBUTE = "location"; - - protected static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay"; - - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } - - @Override - protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String scriptLocation = element.getAttribute(LOCATION_ATTRIBUTE); - String scriptText = DomUtils.getTextValue(element); - if (StringUtils.hasText(scriptLocation) == StringUtils.hasText(scriptText)) { - parserContext.getReaderContext().error( - "Either the 'location' attribute or inline script text must be provided, but not both.", element); - return; - } - - List variableElements = DomUtils.getChildElementsByTagName(element, "variable"); - String scriptVariableGeneratorName = element.getAttribute("script-variable-generator"); - - if (StringUtils.hasText(scriptVariableGeneratorName) && !variableElements.isEmpty()) { - parserContext.getReaderContext().error( - "'script-variable-generator' and 'variable' sub-elements are mutually exclusive.", element); - return; - } - - if (StringUtils.hasText(scriptLocation)) { - builder.addConstructorArgValue(resolveScriptLocation(element, scriptLocation)); - } - else { - builder.addConstructorArgValue(new StaticScriptSource(scriptText)); - } - - BeanMetadataElement scriptVariableGeneratorDef; - if (!StringUtils.hasText(scriptVariableGeneratorName)) { - BeanDefinitionBuilder scriptVariableGeneratorBuilder = - BeanDefinitionBuilder.genericBeanDefinition(DefaultScriptVariableGenerator.class); - ManagedMap variableMap = buildVariablesMap(element, parserContext, variableElements); - if (!CollectionUtils.isEmpty(variableMap)) { - scriptVariableGeneratorBuilder.addConstructorArgValue(variableMap); - } - scriptVariableGeneratorDef = scriptVariableGeneratorBuilder.getBeanDefinition(); - } - else { - scriptVariableGeneratorDef = new RuntimeBeanReference(scriptVariableGeneratorName); - } - - builder.addConstructorArgValue(scriptVariableGeneratorDef); - postProcess(builder, element, parserContext); - } - - /** - * Subclasses may override this no-op method to provide additional configuration. - * - * @param builder The builder. - * @param element The element. - * @param parserContext The parser context. - */ - protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { - } - - private Object resolveScriptLocation(Element element, String scriptLocation) { - String refreshDelayText = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); - String beanClassName = RefreshableResourceScriptSource.class.getName(); - BeanDefinitionBuilder resourceScriptSourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClassName); - resourceScriptSourceBuilder.addConstructorArgValue(scriptLocation); - if (StringUtils.hasText(refreshDelayText)) { - resourceScriptSourceBuilder.addConstructorArgValue(refreshDelayText); - } - else { - resourceScriptSourceBuilder.addConstructorArgValue(-1L); - } - return resourceScriptSourceBuilder.getBeanDefinition(); - } - - private ManagedMap buildVariablesMap(final Element element, final ParserContext parserContext, - List variableElements) { - - @SuppressWarnings("serial") - ManagedMap variableMap = new ManagedMap<>() { - - @Override - public Object put(String key, Object value) { - if (this.containsKey(key)) { - parserContext.getReaderContext().error("Duplicated variable: " + key, element); - } - return super.put(key, value); - } - - }; - - for (Element childElement : variableElements) { - String variableName = childElement.getAttribute("name"); - String variableValue = childElement.getAttribute("value"); - String variableRef = childElement.getAttribute("ref"); - if (StringUtils.hasText(variableValue) == StringUtils.hasText(variableRef)) { - parserContext.getReaderContext().error( - "Exactly one of the 'ref' attribute or 'value' attribute, " + " is required for element " - + IntegrationNamespaceUtils.createElementDescription(element) + ".", element); - } - if (StringUtils.hasText(variableValue)) { - variableMap.put(variableName, variableValue); - } - else { - variableMap.put(variableName, new RuntimeBeanReference(variableRef)); - } - } - - String variables = element.getAttribute("variables"); - if (StringUtils.hasText(variables)) { - String[] variablePairs = StringUtils.commaDelimitedListToStringArray(variables); - for (String variablePair : variablePairs) { - String[] variableValue = variablePair.split("="); - if (variableValue.length != 2) { - parserContext.getReaderContext().error( - "Variable declarations in the 'variable' attribute must have the " - + "form 'var=value'; found : '" + variablePair + "'", element); - } - String variable = variableValue[0].trim(); - String value = variableValue[1]; - if (variable.endsWith("-ref")) { - variable = variable.substring(0, variable.indexOf("-ref")); - variableMap.put(variable, new RuntimeBeanReference(value)); - } - else { - variableMap.put(variable, value); - } - } - } - - return variableMap; - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/ScriptExecutingProcessorFactory.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/ScriptExecutingProcessorFactory.java deleted file mode 100644 index 2689e3e4c42..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/ScriptExecutingProcessorFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config; - -import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.integration.scripting.ScriptVariableGenerator; -import org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor; -import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory; -import org.springframework.scripting.ScriptSource; - -/** - * The factory to create {@link AbstractScriptExecutingMessageProcessor} instances for provided arguments. - * - * @author Artem Bilan - * @since 5.0 - */ -public class ScriptExecutingProcessorFactory { - - public static final String BEAN_NAME = "integrationScriptExecutingProcessorFactory"; - - public AbstractScriptExecutingMessageProcessor createMessageProcessor(String language, - ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator) { - ScriptExecutor scriptExecutor = ScriptExecutorFactory.getScriptExecutor(language); - return new ScriptExecutingMessageProcessor(scriptSource, scriptVariableGenerator, scriptExecutor); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/ScriptNamespaceHandler.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/ScriptNamespaceHandler.java deleted file mode 100644 index 68964572caa..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/ScriptNamespaceHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * @author David Turanski - * @since 2.1 - */ -public class ScriptNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - public void init() { - this.registerBeanDefinitionParser("script", new ScriptParser()); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/ScriptParser.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/ScriptParser.java deleted file mode 100644 index 41854375c45..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/ScriptParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.scripting.config.AbstractScriptParser; -import org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor; -import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory; -import org.springframework.util.StringUtils; - -/** - * An {@link AbstractScriptParser} parser extension for the {@code } tag. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -public class ScriptParser extends AbstractScriptParser { - - private static final String LANGUAGE_ATTRIBUTE = "lang"; - - @Override - protected Class getBeanClass(Element element) { - return ScriptExecutingMessageProcessor.class; - } - - @Override - protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { - String language = element.getAttribute(LANGUAGE_ATTRIBUTE); - String scriptLocation = element.getAttribute(LOCATION_ATTRIBUTE); - if (!StringUtils.hasText(language)) { - if (!StringUtils.hasText(scriptLocation)) { - parserContext.getReaderContext().error( - "An inline script requires the '" + LANGUAGE_ATTRIBUTE + "' attribute.", element); - } - else { - language = ScriptExecutorFactory.deriveLanguageFromFileExtension(scriptLocation); - } - } - builder.addConstructorArgValue(ScriptExecutorFactory.getScriptExecutor(language)); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/package-info.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/package-info.java deleted file mode 100644 index b4eb6c1e1a8..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/jsr223/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for configuration - parsers, namespace handlers. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.scripting.config.jsr223; diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/package-info.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/package-info.java deleted file mode 100644 index 022748d68d2..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Base package supporting configuration. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.scripting.config; diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/DslScriptExecutingMessageProcessor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/DslScriptExecutingMessageProcessor.java deleted file mode 100644 index acb860bf0fa..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/DslScriptExecutingMessageProcessor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.dsl; - -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.io.Resource; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor; -import org.springframework.integration.scripting.RefreshableResourceScriptSource; -import org.springframework.integration.scripting.ScriptVariableGenerator; -import org.springframework.integration.scripting.config.ScriptExecutingProcessorFactory; -import org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor; -import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory; -import org.springframework.messaging.Message; -import org.springframework.scripting.ScriptSource; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * The adapter {@link MessageProcessor} around {@link AbstractScriptExecutingMessageProcessor}. - * Delegates to the {@code GroovyScriptExecutingMessageProcessor}, if provided {@link #lang} - * matches to {@code groovy} string and {@code spring-integration-groovy} jar is in classpath. - * Otherwise, to the {@link ScriptExecutingMessageProcessor}. - * - * @author Artem Bilan - * - * @since 5.0 - */ -class DslScriptExecutingMessageProcessor - implements MessageProcessor, InitializingBean, ApplicationContextAware, BeanClassLoaderAware { - - @SuppressWarnings("NullAway.Init") - private Resource script; - - private @Nullable String location; - - private @Nullable String lang; - - private long refreshCheckDelay = -1; - - @SuppressWarnings("NullAway.Init") - private ScriptVariableGenerator variableGenerator; - - @SuppressWarnings("NullAway.Init") - private ApplicationContext applicationContext; - - @SuppressWarnings("NullAway.Init") - private AbstractScriptExecutingMessageProcessor delegate; - - @SuppressWarnings("NullAway.Init") - private ClassLoader classLoader; - - DslScriptExecutingMessageProcessor(Resource script) { - this.script = script; - } - - DslScriptExecutingMessageProcessor(String location) { - this.location = location; - } - - public void setLang(String lang) { - this.lang = lang; - } - - public void setRefreshCheckDelay(Long refreshCheckDelay) { - this.refreshCheckDelay = refreshCheckDelay; - } - - public void setVariableGenerator(ScriptVariableGenerator variableGenerator) { - this.variableGenerator = variableGenerator; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - @Override - public void afterPropertiesSet() { - if (StringUtils.hasText(this.location)) { - this.script = this.applicationContext.getResource(this.location); - } - - if (!StringUtils.hasText(this.lang)) { - String scriptFilename = this.script.getFilename(); - Assert.hasText(scriptFilename, - () -> "Either 'lang' or file extension must be provided for script: " + this.script); - this.lang = ScriptExecutorFactory.deriveLanguageFromFileExtension(scriptFilename); - } - - ScriptSource scriptSource = new RefreshableResourceScriptSource(this.script, this.refreshCheckDelay); - - if (this.applicationContext.containsBean(ScriptExecutingProcessorFactory.BEAN_NAME)) { - ScriptExecutingProcessorFactory processorFactory = - this.applicationContext.getBean(ScriptExecutingProcessorFactory.BEAN_NAME, - ScriptExecutingProcessorFactory.class); - this.delegate = processorFactory.createMessageProcessor(this.lang, scriptSource, this.variableGenerator); - } - else { - this.delegate = new ScriptExecutingMessageProcessor(scriptSource, this.variableGenerator, - ScriptExecutorFactory.getScriptExecutor(this.lang)); - } - - this.delegate.setBeanFactory(this.applicationContext); - this.delegate.setBeanClassLoader(this.classLoader); - } - - @Override - public @Nullable Object processMessage(Message message) { - return this.delegate.processMessage(message); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/ScriptMessageSourceSpec.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/ScriptMessageSourceSpec.java deleted file mode 100644 index 0ab2c608cbd..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/ScriptMessageSourceSpec.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.dsl; - -import java.util.Collections; -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.io.Resource; -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.dsl.ComponentsRegistration; -import org.springframework.integration.dsl.MessageSourceSpec; -import org.springframework.integration.endpoint.MessageProcessorMessageSource; -import org.springframework.integration.scripting.ScriptVariableGenerator; -import org.springframework.integration.support.MapBuilder; - -/** - * The {@link MessageSourceSpec} for Dynamic Language Scripts. - * Delegates configuration options to the {@link ScriptSpec}. - * Produces {@link MessageProcessorMessageSource}. - * * - * @author Artem Bilan - * - * @since 5.0 - * - * @see ScriptSpec - * @see MessageProcessorMessageSource - */ -public class ScriptMessageSourceSpec extends MessageSourceSpec> - implements ComponentsRegistration { - - private final ScriptSpec delegate; - - public ScriptMessageSourceSpec(Resource scriptResource) { - this.delegate = new ScriptSpec(scriptResource); - } - - public ScriptMessageSourceSpec(String scriptLocation) { - this.delegate = new ScriptSpec(scriptLocation); - } - - /** - * The script lang (Groovy, ruby, python etc.). - * @param lang the script lang - * @return the current spec - * @see ScriptSpec#lang - */ - public ScriptMessageSourceSpec lang(String lang) { - this.delegate.lang(lang); - return this; - } - - /** - * The {@link ScriptVariableGenerator} to use. - * @param variableGenerator the {@link ScriptVariableGenerator} - * @return the current spec - * @see ScriptSpec#variableGenerator(ScriptVariableGenerator) - */ - public ScriptMessageSourceSpec variableGenerator(ScriptVariableGenerator variableGenerator) { - this.delegate.variableGenerator(variableGenerator); - return this; - } - - /** - * The script variables to use. - * @param variables the script variables - * @return the current spec - * @see ScriptSpec#variables(MapBuilder) - */ - public ScriptMessageSourceSpec variables(MapBuilder variables) { - this.delegate.variables(variables); - return this; - } - - /** - * The script variables to use. - * @param variables the script variables - * @return the current spec - * @see ScriptSpec#variables(Map) - */ - public ScriptMessageSourceSpec variables(Map variables) { - this.delegate.variables(variables); - return this; - } - - /** - * The script variable to use. - * @param name the name of variable - * @param value the value of variable - * @return the current spec - * @see ScriptSpec#variable - */ - public ScriptMessageSourceSpec variable(String name, Object value) { - this.delegate.variable(name, value); - return this; - } - - /** - * The refreshCheckDelay in milliseconds for refreshable script resource. - * @param refreshCheckDelay the refresh check delay milliseconds - * @return the current spec - * @see ScriptSpec#refreshCheckDelay - */ - public ScriptMessageSourceSpec refreshCheckDelay(long refreshCheckDelay) { - this.delegate.refreshCheckDelay(refreshCheckDelay); - return this; - } - - @Override - protected MessageSource doGet() { - return new MessageProcessorMessageSource(this.delegate.getObject()); - } - - @Override - public Map getComponentsToRegister() { - return Collections.singletonMap(this.delegate.getObject(), this.delegate.getId()); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/ScriptSpec.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/ScriptSpec.java deleted file mode 100644 index 1d36959cab1..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/ScriptSpec.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.dsl; - -import java.util.HashMap; -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.io.Resource; -import org.springframework.integration.dsl.MessageProcessorSpec; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.scripting.DefaultScriptVariableGenerator; -import org.springframework.integration.scripting.ScriptVariableGenerator; -import org.springframework.integration.support.MapBuilder; -import org.springframework.util.Assert; - -/** - * The {@link MessageProcessorSpec} implementation for the {@link DslScriptExecutingMessageProcessor}. - * - * @author Artem Bilan - * - * @since 5.0 - */ -public class ScriptSpec extends MessageProcessorSpec { - - private final DslScriptExecutingMessageProcessor processor; - - private @Nullable ScriptVariableGenerator variableGenerator; - - private final Map variables = new HashMap<>(); - - ScriptSpec(Resource scriptResource) { - Assert.notNull(scriptResource, "'scriptResource' must not be null"); - this.processor = new DslScriptExecutingMessageProcessor(scriptResource); - } - - ScriptSpec(String scriptLocation) { - Assert.hasText(scriptLocation, "'scriptLocation' must not be empty"); - this.processor = new DslScriptExecutingMessageProcessor(scriptLocation); - } - - /** - * The script lang (Groovy, ruby, python etc.). - * @param lang the script lang - * @return the current spec - * @see DslScriptExecutingMessageProcessor#setLang - */ - public ScriptSpec lang(String lang) { - Assert.hasText(lang, "'lang' must not be empty"); - this.processor.setLang(lang); - return this; - } - - /** - * The refreshCheckDelay in milliseconds for refreshable script resource. - * @param refreshCheckDelay the refresh check delay milliseconds - * @return the current spec - * @see org.springframework.integration.scripting.RefreshableResourceScriptSource - */ - public ScriptSpec refreshCheckDelay(long refreshCheckDelay) { - this.processor.setRefreshCheckDelay(refreshCheckDelay); - return this; - } - - /** - * The {@link ScriptVariableGenerator} to use. - * @param variableGenerator the {@link ScriptVariableGenerator} - * @return the current spec - * @see org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor - */ - public ScriptSpec variableGenerator(ScriptVariableGenerator variableGenerator) { - Assert.notNull(variableGenerator, "'variableGenerator' must not be null"); - Assert.state(this.variables.isEmpty(), "'variableGenerator' and 'variables' are mutually exclusive"); - this.variableGenerator = variableGenerator; - return this; - } - - /** - * The script variables to use. - * @param variables the script variables {@link MapBuilder} - * @return the current spec - * @see DefaultScriptVariableGenerator - */ - public ScriptSpec variables(MapBuilder variables) { - return variables(variables.get()); - } - - /** - * The script variables to use - * @param variables the script variables {@link Map} - * @return the current spec - * @see DefaultScriptVariableGenerator - */ - public ScriptSpec variables(Map variables) { - Assert.notEmpty(variables, "'variables' must not be empty"); - Assert.state(this.variableGenerator == null, "'variableGenerator' and 'variables' are mutually exclusive"); - this.variables.putAll(variables); - return this; - } - - /** - * The script variable to use. - * @param name the name of variable - * @param value the value of variable - * @return the current spec - * @see DefaultScriptVariableGenerator - */ - public ScriptSpec variable(String name, Object value) { - Assert.hasText(name, "'name' must not be empty"); - Assert.state(this.variableGenerator == null, "'variableGenerator' and 'variables' are mutually exclusive"); - this.variables.put(name, value); - return this; - } - - @Override - protected MessageProcessor doGet() { - if (this.variableGenerator == null) { - this.variableGenerator = new DefaultScriptVariableGenerator(this.variables); - } - this.processor.setVariableGenerator(this.variableGenerator); - return this.processor; - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/Scripts.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/Scripts.java deleted file mode 100644 index eadc4d174a9..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/Scripts.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.dsl; - -import org.springframework.core.io.Resource; - -/** - * The factory for Dynamic Language Scripts (Groovy, Ruby, Python, JavaScript etc.). - * - * @author Artem Bilan - * - * @since 5.0 - */ -public final class Scripts { - - /** - * The factory method to produce {@link ScriptSpec} based on the {@link Resource}. - * The {@link Resource} must represent the real file and can be injected like: - *
-	 *  @Value("com/my/project/scripts/FilterScript.groovy")
-	 *  private Resource filterScript;
-	 * 
- * @param scriptResource the script file {@link Resource} - * @return the ScriptSpec instance - */ - public static ScriptSpec processor(Resource scriptResource) { - return new ScriptSpec(scriptResource); - } - - /** - * The factory method to produce {@link ScriptSpec} based on the script file location. - * @param scriptLocation the path to the script file. - * {@code file:}, {@code ftp:}, {@code s3:} etc. - * The {@code classpath:} can be omitted. - * @return the ScriptSpec instance - */ - public static ScriptSpec processor(String scriptLocation) { - return new ScriptSpec(scriptLocation); - } - - /** - * Factory for the {@link ScriptMessageSourceSpec} based on the {@link Resource}. - * The {@link Resource} must represent the real file and can be injected like: - *
-	 *  @Value("com/my/project/scripts/FilterScript.groovy")
-	 *  private Resource filterScript;
-	 * 
- * @param scriptResource the script {@link Resource} - * @return the {@link ScriptMessageSourceSpec} - */ - public static ScriptMessageSourceSpec messageSource(Resource scriptResource) { - return new ScriptMessageSourceSpec(scriptResource); - } - - /** - * Factory for the {@link ScriptMessageSourceSpec} based on the script location. - * @param scriptLocation the path to the script file. - * {@code file:}, {@code ftp:}, {@code s3:} etc. - * The {@code classpath:} can be omitted. - * @return the {@link ScriptMessageSourceSpec} - */ - public static ScriptMessageSourceSpec messageSource(String scriptLocation) { - return new ScriptMessageSourceSpec(scriptLocation); - } - - private Scripts() { - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/package-info.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/package-info.java deleted file mode 100644 index a606f67f301..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides Scripting Components support for Spring Integration Java DSL. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.scripting.dsl; diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/AbstractScriptExecutor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/AbstractScriptExecutor.java deleted file mode 100644 index 6f85d663341..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/AbstractScriptExecutor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import java.util.Date; -import java.util.Map; - -import javax.script.Bindings; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.SimpleBindings; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.integration.scripting.ScriptingException; -import org.springframework.scripting.ScriptSource; -import org.springframework.util.Assert; - -/** - * Base Class for {@link ScriptExecutor}. - * - * @author David Turanski - * @author Mark Fisher - * @author Artem Bilan - * @author Gary Russell - * @author Ngoc Nhan - * - * @since 2.1 - */ -public abstract class AbstractScriptExecutor implements ScriptExecutor { - - protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - final - - private final ScriptEngine scriptEngine; - - protected AbstractScriptExecutor(String language) { - Assert.hasText(language, "language must not be empty"); - this.scriptEngine = new ScriptEngineManager().getEngineByName(language); - Assert.notNull(this.scriptEngine, () -> invalidLanguageMessage(language)); - if (this.logger.isDebugEnabled()) { - this.logger.debug("Using script engine : " + this.scriptEngine.getFactory().getEngineName()); - } - } - - protected AbstractScriptExecutor(ScriptEngine scriptEngine) { - Assert.notNull(scriptEngine, "'scriptEngine' must not be null."); - this.scriptEngine = scriptEngine; - } - - public ScriptEngine getScriptEngine() { - return this.scriptEngine; - } - - @Override - public @Nullable Object executeScript(ScriptSource scriptSource, @Nullable Map variables) { - try { - Object result; - String script = scriptSource.getScriptAsString(); - Date start = new Date(); - if (this.logger.isDebugEnabled()) { - this.logger.debug("executing script: " + script); - } - - Bindings bindings = null; - if (variables != null && !variables.isEmpty()) { - bindings = new SimpleBindings(variables); - result = this.scriptEngine.eval(script, bindings); - } - else { - result = this.scriptEngine.eval(script); - } - - result = postProcess(result, this.scriptEngine, script, bindings); - - if (this.logger.isDebugEnabled()) { - this.logger.debug("script executed in " + (new Date().getTime() - start.getTime()) + " ms"); - } - return result; - } - catch (Exception e) { - throw new ScriptingException(e.getMessage(), e); - } - } - - /** - * Subclasses may implement this to provide any special handling required - * @param result the result. - * @param scriptEngine the engine. - * @param script the script. - * @param bindings the bindings. - * @return modified result - */ - protected abstract Object postProcess(Object result, ScriptEngine scriptEngine, String script, @Nullable Bindings bindings); - - private static String invalidLanguageMessage(String language) { - return ScriptEngineManager.class.getName() + - " is unable to create a script engine for language '" + language + "'.\n" + - "This may be due to a missing language implementation or an invalid language name."; - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/DefaultScriptExecutor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/DefaultScriptExecutor.java deleted file mode 100644 index 0f448530518..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/DefaultScriptExecutor.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import javax.script.Bindings; -import javax.script.ScriptEngine; - -import org.jspecify.annotations.Nullable; - -/** - * Default implementation of the {@link AbstractScriptExecutor}. - * Accepts a scripting language for resolving a target {@code ScriptEngine} for - * evaluation and does nothing with the {@code result} in the - * {@link #postProcess(Object, ScriptEngine, String, Bindings)} implementation. - * - * @author David Turanski - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class DefaultScriptExecutor extends AbstractScriptExecutor { - - /** - * Create a DefaultScriptExecutor for the specified language name (JSR233 alias). - * @param language the scripting language identificator. - */ - public DefaultScriptExecutor(String language) { - super(language); - } - - @Override - protected Object postProcess(Object result, ScriptEngine scriptEngine, String script, @Nullable Bindings bindings) { - return result; - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/RubyScriptExecutor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/RubyScriptExecutor.java deleted file mode 100644 index 63cd197a69a..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/RubyScriptExecutor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -/** - * A {@link DefaultScriptExecutor} extension for Ruby scripting support. - * It is present here only for the reason to populate - * {@code org.jruby.embed.localvariable.behavior} and - * {@code org.jruby.embed.localcontext.scope} system properties. - * May be revised in the future. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - * - */ -public class RubyScriptExecutor extends DefaultScriptExecutor { - - static { - System.setProperty("org.jruby.embed.localvariable.behavior", "transient"); - System.setProperty("org.jruby.embed.localcontext.scope", "threadsafe"); - } - - public RubyScriptExecutor() { - super("ruby"); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutingMessageProcessor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutingMessageProcessor.java deleted file mode 100644 index 78c6a27c011..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutingMessageProcessor.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor; -import org.springframework.integration.scripting.DefaultScriptVariableGenerator; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.integration.scripting.ScriptVariableGenerator; -import org.springframework.messaging.Message; -import org.springframework.scripting.ScriptSource; -import org.springframework.util.Assert; - -/** - * An {@link AbstractScriptExecutingMessageProcessor} implementation for evaluating scripts - * from the provided {@link ScriptSource} in the provided {@link ScriptExecutor} against an optional - * binding {@code variables}. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor { - - private final ScriptExecutor scriptExecutor; - - private final ScriptSource scriptSource; - - /** - * Create a processor for the {@link ScriptSource} using the provided - * {@link ScriptExecutor} using the DefaultScriptVariableGenerator - * @param scriptSource The script source. - * @param scriptExecutor The script executor. - */ - public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptExecutor scriptExecutor) { - this(scriptSource, new DefaultScriptVariableGenerator(), scriptExecutor); - } - - /** - * Create a processor for the {@link ScriptSource} using the provided - * {@link ScriptExecutor} - * @param scriptSource The script source. - * @param scriptVariableGenerator The script variable generator. - * @param scriptExecutor The script executor. - */ - public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator, - ScriptExecutor scriptExecutor) { - - super(scriptVariableGenerator); - Assert.notNull(scriptSource, "'scriptSource' must not be null"); - Assert.notNull(scriptExecutor, "'scriptExecutor' must not be null"); - this.scriptSource = scriptSource; - this.scriptExecutor = scriptExecutor; - } - - /** - * Create a processor for the {@link ScriptSource} using the provided - * {@link ScriptExecutor} using the DefaultScriptVariableGenerator - * @param scriptSource The script source. - * @param scriptExecutor The script executor. - * @param variables The variables. - */ - public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptExecutor scriptExecutor, - Map variables) { - - this(scriptSource, new DefaultScriptVariableGenerator(variables), scriptExecutor); - } - - @Override - protected ScriptSource getScriptSource(Message message) { - return this.scriptSource; - } - - @Override - protected @Nullable Object executeScript(ScriptSource scriptSource, Map variables) { - Assert.notNull(scriptSource, "scriptSource must not be null"); - return this.scriptExecutor.executeScript(scriptSource, variables); - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java deleted file mode 100644 index 22792ac1298..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.integration.scripting.PolyglotScriptExecutor; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.util.Assert; - -/** - * The scripting configuration utilities. - * - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -public final class ScriptExecutorFactory { - - private static final Log LOGGER = LogFactory.getLog(ScriptExecutorFactory.class); - - public static ScriptExecutor getScriptExecutor(String language) { - if (language.equalsIgnoreCase("jython")) { - LOGGER.warn(""" - The 'jython' language indicator is deprecated and will be removed in the next version. - The Python support is fully based on GraalVM Polyglot and there is no 'jython' dependency requirement any more. - """); - return new PolyglotScriptExecutor("python"); - } - else if (language.equalsIgnoreCase("python")) { - return new PolyglotScriptExecutor("python"); - } - else if (language.equalsIgnoreCase("ruby") || language.equalsIgnoreCase("jruby")) { - return new RubyScriptExecutor(); - } - else if (language.equalsIgnoreCase("js") || language.equalsIgnoreCase("javascript")) { - return new PolyglotScriptExecutor("js"); - } - return new DefaultScriptExecutor(language); - } - - /** - * Derive a scripting language from the provided script file name. - * @param scriptLocation the script file to consult for extension. - * @return the language name for the {@link ScriptExecutor}. - * @since 5.2 - */ - public static String deriveLanguageFromFileExtension(String scriptLocation) { - int index = scriptLocation.lastIndexOf('.') + 1; - Assert.state(index > 0, () -> "Unable to determine language for script '" + scriptLocation + "'"); - String extension = scriptLocation.substring(index); - switch (extension) { - case "kts" -> { - return "kotlin"; - } - case "js" -> { - return "js"; - } - case "py" -> { - return "python"; - } - } - ScriptEngineManager engineManager = new ScriptEngineManager(); - ScriptEngine engine = engineManager.getEngineByExtension(extension); - Assert.state(engine != null, () -> "No suitable scripting engine found for extension '" + extension + "'"); - return engine.getFactory().getLanguageName(); - } - - private ScriptExecutorFactory() { - } - -} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/package-info.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/package-info.java deleted file mode 100644 index 38a8df76f6e..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting JSR223 Scripting. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.scripting.jsr223; diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/package-info.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/package-info.java deleted file mode 100644 index 529587b578f..00000000000 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Base package for scripting support. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.scripting; diff --git a/spring-integration-scripting/src/main/resources/META-INF/spring.handlers b/spring-integration-scripting/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index bc3dff4203e..00000000000 --- a/spring-integration-scripting/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/scripting=org.springframework.integration.scripting.config.jsr223.ScriptNamespaceHandler diff --git a/spring-integration-scripting/src/main/resources/META-INF/spring.schemas b/spring-integration-scripting/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index bcb38ff0b43..00000000000 --- a/spring-integration-scripting/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,42 +0,0 @@ -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-2.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-2.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-3.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.3.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-5.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-5.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-5.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-2.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-2.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-3.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-4.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-4.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-4.3.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-5.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-5.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-5.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -http\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-2.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-2.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-3.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-4.3.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-5.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-5.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-5.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting.xsd=org/springframework/integration/scripting/config/spring-integration-scripting.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-2.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-2.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-3.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-4.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-4.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-4.3.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-5.0.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-5.1.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core-5.2.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd -https\://www.springframework.org/schema/integration/scripting/spring-integration-scripting-core.xsd=org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd diff --git a/spring-integration-scripting/src/main/resources/META-INF/spring.tooling b/spring-integration-scripting/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index 14bf96f34fc..00000000000 --- a/spring-integration-scripting/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration scripting namespace -http\://www.springframework.org/schema/integration/scripting@name=integration scripting Namespace -http\://www.springframework.org/schema/integration/scripting@prefix=int-script -http\://www.springframework.org/schema/integration/scripting@icon=org/springframework/integration/scripting/config/spring-integration-scripting.gif diff --git a/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd b/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd deleted file mode 100644 index 3f3e5f1211d..00000000000 --- a/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting-core.xsd +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - Allows you to define custom script variable bindings. The use of this - sub-element is mutually - exclusive with the 'script-variable-generator' attribute. - - - - - - Name of the script variable. - - - - - - - Value of the script variable as a literal. - - - - - - - Value of the script variable as a bean reference. - - - - - - - - - - - - - Resource location path for the Script. Either this or an inline script - as body text should be provided, but not both. - - - - - - - Reference to the ScriptVariableGenerator bean. This attribute is mutually - exclusive with any 'variable' sub-elements and 'variables' attribute. - - - - - - - - - - - Refresh delay for the script contents if specified as a resource - location (defaults to -1, never refresh). - - - - - - - Comma-delimited pairs of variables and their values. - the variable name can applies '-ref' suffix, which mean to determine - a variable value as a bean reference. - This attribute isn't mutually exclusive with 'variable' sub-elements - and all variables will be merged to one Map. - This attribute is mutually exclusive with 'script-variable-generator' attribute. - - - - - - diff --git a/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting.gif b/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting.gif deleted file mode 100644 index c4b81739ebf..00000000000 Binary files a/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting.gif and /dev/null differ diff --git a/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting.xsd b/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting.xsd deleted file mode 100644 index 6dcca5758f3..00000000000 --- a/spring-integration-scripting/src/main/resources/org/springframework/integration/scripting/config/spring-integration-scripting.xsd +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - Configures an inner bean - ('org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor') - that will generate a JSR 223 compliant script. - - - - - - - - - - - The script language or JSR 223 scripting engine name. Required only for inline scripts. - If a script location is referenced, the language may be derived from the file extension - (.rb: ruby, .groovy, js: javascript (ECMAScript), .py: python). - - - - - - - The script variable to return as a result of the evaluation (Optional). - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Int3164Jsr223RefreshTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Int3164Jsr223RefreshTests-context.xml deleted file mode 100644 index 782fb0b5b1c..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Int3164Jsr223RefreshTests-context.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Int3164Jsr223RefreshTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Int3164Jsr223RefreshTests.java deleted file mode 100644 index 23e6fac96d3..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Int3164Jsr223RefreshTests.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import java.io.File; -import java.io.IOException; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 3.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class Int3164Jsr223RefreshTests { - - private static File workDir; - - private static File scriptFile; - - @Autowired - private MessageChannel referencedScriptInput; - - @Autowired - private PollableChannel outputChannel; - - @BeforeAll - public static void start() throws IOException { - String basePath = System.getProperty("java.io.tmpdir") + File.separator + "Int3164Jsr223RefreshTests"; - workDir = new File(basePath); - shutdown(); - workDir.mkdir(); - workDir.deleteOnExit(); - scriptFile = new File(workDir, "int3164.groovy"); - scriptFile.createNewFile(); - FileCopyUtils.copy("1".getBytes(), scriptFile); - } - - @AfterAll - public static void shutdown() { - if (workDir != null && workDir.exists()) { - for (File file : workDir.listFiles()) { - file.delete(); - } - workDir.delete(); - } - } - - @Test - public void testRefreshingScript() throws Exception { - this.referencedScriptInput.send(new GenericMessage("test")); - assertThat(this.outputChannel.receive(100).getPayload()).isEqualTo(1); - - FileCopyUtils.copy("2".getBytes(), scriptFile); - scriptFile.setLastModified(System.currentTimeMillis() + 10000); // force refresh - - this.referencedScriptInput.send(new GenericMessage("test")); - assertThat(this.outputChannel.receive(100).getPayload()).isEqualTo(2); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests-context.xml deleted file mode 100644 index 7f53b027b2d..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests-context.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests.groovy b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests.groovy deleted file mode 100644 index 06a775dfb29..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests.groovy +++ /dev/null @@ -1 +0,0 @@ -headers.type == 'good' diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests.java deleted file mode 100644 index ab319ce7cf9..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223FilterTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223FilterTests { - - @Autowired - private MessageChannel referencedScriptInput; - - @Autowired - private MessageChannel inlineScriptInput; - - @Test - public void referencedScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - Message message1 = MessageBuilder.withPayload("test-1") - .setReplyChannel(replyChannel) - .setHeader("type", "bad") - .build(); - Message message2 = MessageBuilder.withPayload("test-2") - .setReplyChannel(replyChannel) - .setHeader("type", "good") - .build(); - this.referencedScriptInput.send(message1); - this.referencedScriptInput.send(message2); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("test-2"); - assertThat(replyChannel.receive(0)).isNull(); - } - - @Test - public void inlineScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - Message message1 = MessageBuilder.withPayload("bad").setReplyChannel(replyChannel).build(); - Message message2 = MessageBuilder.withPayload("good").setReplyChannel(replyChannel).build(); - this.inlineScriptInput.send(message1); - this.inlineScriptInput.send(message2); - Message received = replyChannel.receive(0); - assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo("good"); - assertThat(received).isEqualTo(message2); - assertThat(replyChannel.receive(0)).isNull(); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests-context.xml deleted file mode 100644 index 7a671d9a966..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests-context.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java deleted file mode 100644 index 1e9103b7497..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Oleg Zhurakousky - * @author David Turanski - * @author Gunnar Hillert - * @author Artem Bilan - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223HeaderEnricherTests { - - @Autowired - private MessageChannel inputA; - - @Autowired - private QueueChannel outputA; - - @Autowired - private MessageChannel inputB; - - @Autowired - private QueueChannel outputB; - - @Test - public void referencedScript() { - inputA.send(new GenericMessage<>("Hello")); - assertThat(outputA.receive(20000).getHeaders().get("TEST_HEADER")).isEqualTo("jruby"); - } - - @Test - public void inlineScript() { - inputB.send(new GenericMessage<>("Hello")); - assertThat(outputB.receive(20000).getHeaders().get("TEST_HEADER")).isEqualTo("js"); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.rb b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.rb deleted file mode 100644 index 0e89115cb41..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.rb +++ /dev/null @@ -1 +0,0 @@ -"jruby" diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests-context.xml deleted file mode 100644 index 83677b05b12..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests-context.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - -
- - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java deleted file mode 100644 index 6042117eb7b..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import java.util.Date; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.messaging.Message; -import org.springframework.messaging.PollableChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * @author Gary Russell - * - * @since 2.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223InboundChannelAdapterTests { - - @Autowired - @Qualifier("inbound-channel-adapter-channel") - private PollableChannel inboundChannelAdapterChannel; - - @Test - public void testInt2867InboundChannelAdapter() throws Exception { - Message message = this.inboundChannelAdapterChannel.receive(20000); - assertThat(message).isNotNull(); - Object payload = message.getPayload(); - Thread.sleep(2); - assertThat(payload).isInstanceOf(Date.class); - assertThat(((Date) payload).before(new Date())).isTrue(); - assertThat(message.getHeaders().get("foo")).isEqualTo("bar"); - - message = this.inboundChannelAdapterChannel.receive(20000); - assertThat(message).isNotNull(); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RefreshTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RefreshTests-context.xml deleted file mode 100644 index edc2247a3e5..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RefreshTests-context.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RefreshTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RefreshTests.java deleted file mode 100644 index 20e46fde34a..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RefreshTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import java.beans.PropertyEditorSupport; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.AbstractResource; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223RefreshTests { - - @Autowired - private MessageChannel referencedScriptInput; - - @Test - public void referencedScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - this.referencedScriptInput.send(MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build()); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("ruby-test-1"); - this.referencedScriptInput.send(MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build()); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("ruby-test-0"); - assertThat(replyChannel.receive(0)).isNull(); - } - - public static class ResourceEditor extends PropertyEditorSupport { - - @Override - public void setAsText(String text) throws IllegalArgumentException { - super.setValue(new CycleResource()); - } - - } - - private static class CycleResource extends AbstractResource { - - private int count = -1; - - private final String[] scripts = {"\"ruby-#{payload}-0\"", "\"ruby-#{payload}-1\""}; - - CycleResource() { - super(); - } - - @Override - public String getDescription() { - return "CycleResource"; - } - - @Override - public String getFilename() throws IllegalStateException { - return "CycleResource"; - } - - @Override - public long lastModified() { - return -1; - } - - @Override - public InputStream getInputStream() { - if (++count > scripts.length - 1) { - count = 0; - } - return new ByteArrayInputStream(scripts[count].getBytes()); - } - - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests-context.xml deleted file mode 100644 index 174dd88c26c..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests-context.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - payload.length() > 5 ? 'longStrings' : 'shortStrings' - - - - - - - payload.length() > 5 ? 'longStrings' : 'shortStrings' - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.groovy b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.groovy deleted file mode 100644 index 48854116aec..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.groovy +++ /dev/null @@ -1 +0,0 @@ -payload.length() > maxLen as int ? 'longStrings' : 'shortStrings' diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.java deleted file mode 100644 index 783dc795b8f..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author David Turanski - * @author Artem Bilan - * - * @since 2.1 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223RouterTests { - - @Autowired - private MessageChannel referencedScriptInput; - - @Autowired - private MessageChannel inlineScriptInput; - - @Autowired - private MessageChannel scriptRouterWithinChainInput; - - @Autowired - private QueueChannel longStrings; - - @Autowired - private QueueChannel shortStrings; - - @AfterEach - void cleanUp() { - this.longStrings.clear(); - this.shortStrings.clear(); - } - - @Test - public void referencedScript() { // long is > 3 - Message message1 = new GenericMessage<>("aardvark"); - Message message2 = new GenericMessage<>("bear"); - Message message3 = new GenericMessage<>("cat"); - Message message4 = new GenericMessage<>("dog"); - Message message5 = new GenericMessage<>("elephant"); - this.referencedScriptInput.send(message1); - this.referencedScriptInput.send(message2); - this.referencedScriptInput.send(message3); - this.referencedScriptInput.send(message4); - this.referencedScriptInput.send(message5); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("cat"); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("dog"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("aardvark"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("bear"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("elephant"); - assertThat(shortStrings.receive(0)).isNull(); - assertThat(longStrings.receive(0)).isNull(); - } - - @Test - public void inlineScript() { // long is > 5 - Message message1 = new GenericMessage<>("aardvark"); - Message message2 = new GenericMessage<>("bear"); - Message message3 = new GenericMessage<>("cat"); - Message message4 = new GenericMessage<>("dog"); - Message message5 = new GenericMessage<>("elephant"); - this.inlineScriptInput.send(message1); - this.inlineScriptInput.send(message2); - this.inlineScriptInput.send(message3); - this.inlineScriptInput.send(message4); - this.inlineScriptInput.send(message5); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("bear"); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("cat"); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("dog"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("aardvark"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("elephant"); - assertThat(shortStrings.receive(0)).isNull(); - assertThat(longStrings.receive(0)).isNull(); - } - - @Test - public void testInt2893ScriptRouterWithinChain() { - Message message1 = new GenericMessage<>("aardvark"); - Message message2 = new GenericMessage<>("bear"); - Message message3 = new GenericMessage<>("cat"); - Message message4 = new GenericMessage<>("dog"); - Message message5 = new GenericMessage<>("elephant"); - this.scriptRouterWithinChainInput.send(message1); - this.scriptRouterWithinChainInput.send(message2); - this.scriptRouterWithinChainInput.send(message3); - this.scriptRouterWithinChainInput.send(message4); - this.scriptRouterWithinChainInput.send(message5); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("bear"); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("cat"); - assertThat(shortStrings.receive(0).getPayload()).isEqualTo("dog"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("aardvark"); - assertThat(longStrings.receive(0).getPayload()).isEqualTo("elephant"); - assertThat(shortStrings.receive(0)).isNull(); - assertThat(longStrings.receive(0)).isNull(); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-context.xml deleted file mode 100644 index b1ac8fc7a92..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-context.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-fail-duplicated-variable-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-fail-duplicated-variable-context.xml deleted file mode 100644 index e20d09a1cde..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-fail-duplicated-variable-context.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-fail-withgenerator-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-fail-withgenerator-context.xml deleted file mode 100644 index f6ab488d08a..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests-fail-withgenerator-context.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java deleted file mode 100644 index 3c7bde75e1c..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.scripting.ScriptVariableGenerator; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author David Turanski - * @author Gunnar Hillert - * @author Artem Bilan - * - * @since 2.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223ServiceActivatorTests { - - @Autowired - private MessageChannel referencedScriptInput; - - @Autowired - private MessageChannel inlineScriptInput; - - @Autowired - private MessageChannel withScriptVariableGenerator; - - @Test - public void referencedScript() throws Exception { - - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - for (int i = 1; i <= 3; i++) { - Message message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build(); - this.referencedScriptInput.send(message); - Thread.sleep(1000); - } - String value1 = (String) replyChannel.receive(0).getPayload(); - String value2 = (String) replyChannel.receive(0).getPayload(); - String value3 = (String) replyChannel.receive(0).getPayload(); - assertThat(value1.startsWith("python-test-1-foo (foo2) - bar")).isTrue(); - assertThat(value2.startsWith("python-test-2-foo (foo2) - bar")).isTrue(); - assertThat(value3.startsWith("python-test-3-foo (foo2) - bar")).isTrue(); - - // because we are using 'prototype bean the suffix date will be - // different - assertThat(value1.substring(value1.indexOf(":") + 1, value1.lastIndexOf(":")) - .equals(value2.substring(value1.indexOf(":") + 1, value1.lastIndexOf(":")))).isFalse(); - assertThat(value1.substring(value1.indexOf(":") + 1, value1.lastIndexOf(":")) - .equals(value1.substring(value1.lastIndexOf(":")))).isFalse(); - - assertThat(value2.substring(value1.indexOf(":") + 1, value1.lastIndexOf(":")) - .equals(value3.substring(value1.indexOf(":") + 1, value1.lastIndexOf(":")))).isFalse(); - - assertThat(value1.substring(value1.lastIndexOf(":") + 1) - .equals(value2.substring(value1.lastIndexOf(":") + 1))).isFalse(); - assertThat(value2.substring(value1.lastIndexOf(":") + 1) - .equals(value3.substring(value1.lastIndexOf(":") + 1))).isFalse(); - - assertThat(replyChannel.receive(0)).isNull(); - } - - @Test - public void withScriptVariableGenerator() throws Exception { - - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - for (int i = 1; i <= 3; i++) { - Message message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build(); - this.withScriptVariableGenerator.send(message); - Thread.sleep(1000); - } - String value1 = (String) replyChannel.receive(0).getPayload(); - String value2 = (String) replyChannel.receive(0).getPayload(); - String value3 = (String) replyChannel.receive(0).getPayload(); - assertThat(value1.startsWith("ruby-test-1-foo - bar")).isTrue(); - assertThat(value2.startsWith("ruby-test-2-foo - bar")).isTrue(); - assertThat(value3.startsWith("ruby-test-3-foo - bar")).isTrue(); - // because we are using 'prototype bean the suffix date will be - // different - - assertThat(value1.substring(26).equals(value2.substring(26))).isFalse(); - assertThat(value2.substring(26).equals(value3.substring(26))).isFalse(); - - assertThat(replyChannel.receive(0)).isNull(); - } - - @Test - public void inlineScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - for (int i = 1; i <= 3; i++) { - Message message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build(); - this.inlineScriptInput.send(message); - } - String payload = (String) replyChannel.receive(0).getPayload(); - - assertThat(payload).startsWith("inline-test-1 - FOO"); - - payload = (String) replyChannel.receive(0).getPayload(); - assertThat(payload).startsWith("inline-test-2 - FOO"); - - payload = (String) replyChannel.receive(0).getPayload(); - assertThat(payload).startsWith("inline-test-3 - FOO"); - assertThat(payload.substring(payload.indexOf(":") + 1).matches(".+\\d{2}:\\d{2}:\\d{2}.+")).isTrue(); - - assertThat(replyChannel.receive(0)).isNull(); - - } - - @Test - public void variablesAndScriptVariableGenerator() { - assertThatExceptionOfType(BeansException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext( - "Jsr223ServiceActivatorTests-fail-withgenerator-context.xml", - getClass())) - .withMessageContaining( - "'script-variable-generator' and 'variable' sub-elements are mutually exclusive."); - } - - @Test - public void testDuplicateVariable() { - assertThatExceptionOfType(BeansException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext( - "Jsr223ServiceActivatorTests-fail-duplicated-variable-context.xml", - getClass())) - .withMessageContaining("Duplicated variable: foo"); - } - - public static class SampleScriptVariableSource implements ScriptVariableGenerator { - - @Override - public Map generateScriptVariables(Message message) { - Map variables = new HashMap<>(); - variables.put("foo", "foo"); - variables.put("bar", "bar"); - variables.put("date", new Date()); - variables.put("payload", message.getPayload()); - variables.put("headers", message.getHeaders()); - return variables; - } - - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.py b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.py deleted file mode 100644 index 4617c17a744..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.py +++ /dev/null @@ -1 +0,0 @@ -"python-%s-%s (%s) - %s - :%s:%s" %(payload,foo,foo2,bar,date,date2) diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.rb b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.rb deleted file mode 100644 index e181372d9f0..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.rb +++ /dev/null @@ -1 +0,0 @@ -"ruby-#{payload}-#{foo} - #{bar} - #{date}" diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests-context.xml deleted file mode 100644 index 812f3f4812b..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests-context.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests.groovy b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests.groovy deleted file mode 100644 index 803d916e18c..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests.groovy +++ /dev/null @@ -1 +0,0 @@ -payload.split(',') diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests.java deleted file mode 100644 index c31ede9131b..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223SplitterTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223SplitterTests { - - @Autowired - private MessageChannel referencedScriptInput; - - @Autowired - private MessageChannel inlineScriptInput; - - @Test - public void referencedScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - Message message = MessageBuilder.withPayload("x,y,z").setReplyChannel(replyChannel).build(); - this.referencedScriptInput.send(message); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("x"); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("y"); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("z"); - assertThat(replyChannel.receive(0)).isNull(); - } - - @Test - public void inlineScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - Message message = MessageBuilder.withPayload("a b c").setReplyChannel(replyChannel).build(); - this.inlineScriptInput.send(message); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("a"); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("b"); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("c"); - assertThat(replyChannel.receive(0)).isNull(); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests-context.xml deleted file mode 100644 index 953e7ddd8ec..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests-context.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java deleted file mode 100644 index c627761aadb..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.config.jsr223; - -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class Jsr223TransformerTests { - - @Autowired - private MessageChannel referencedScriptInput; - - @Autowired - private MessageChannel inlineScriptInput; - - @Autowired - private MessageChannel int3162InputChannel; - - @Autowired - private PollableChannel int3162OutputChannel; - - @Test - public void referencedScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - for (int i = 1; i <= 3; i++) { - Message message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build(); - this.referencedScriptInput.send(message); - } - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("ruby-test-1"); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("ruby-test-2"); - assertThat(replyChannel.receive(0).getPayload()).isEqualTo("ruby-test-3"); - assertThat(replyChannel.receive(0)).isNull(); - } - - @Test - public void inlineScript() { - QueueChannel replyChannel = new QueueChannel(); - replyChannel.setBeanName("returnAddress"); - for (int i = 1; i <= 3; i++) { - Message message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build(); - this.inlineScriptInput.send(message); - } - assertThat(replyChannel.receive(0).getPayload().toString()).isEqualTo("inline-test-1"); - assertThat(replyChannel.receive(0).getPayload().toString()).isEqualTo("inline-test-2"); - assertThat(replyChannel.receive(0).getPayload().toString()).isEqualTo("inline-test-3"); - assertThat(replyChannel.receive(0)).isNull(); - } - - @Test - public void testInt3162ScriptExecutorThreadSafety() { - for (int i = 0; i < 100; i++) { - this.int3162InputChannel.send(new GenericMessage(i)); - } - - Set result = new HashSet<>(); - - for (int i = 0; i < 100; i++) { - Message message = this.int3162OutputChannel.receive(10000); - result.add(message.getPayload()); - } - - assertThat(result.size()).isEqualTo(100); - - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.rb b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.rb deleted file mode 100644 index 07c8943464d..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Transformer - def transform(payload) - "ruby-#{payload}" - end -end - -Transformer.new.transform(payload) diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/dsl/ScriptsTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/dsl/ScriptsTests.java deleted file mode 100644 index 25c4c6afffa..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/dsl/ScriptsTests.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.dsl; - -import java.io.File; -import java.io.IOException; -import java.util.Date; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.core.io.Resource; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.channel.QueueChannelOperations; -import org.springframework.integration.config.EnableIntegration; -import org.springframework.integration.dsl.IntegrationFlow; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Artem Bilan - * - * @since 5.0 - */ -@SpringJUnitConfig -@DirtiesContext -public class ScriptsTests { - - @TempDir - public static File FOLDER; - - private static File SCRIPT_FILE; - - @Autowired - @Qualifier("scriptSplitter.input") - private MessageChannel splitterInput; - - @Autowired - @Qualifier("scriptTransformer.input") - private MessageChannel transformerInput; - - @Autowired - @Qualifier("scriptFilter.input") - private MessageChannel filterInput; - - @Autowired - private PollableChannel discardChannel; - - @Autowired - @Qualifier("scriptService.input") - private MessageChannel scriptServiceInput; - - @Autowired - @Qualifier("scriptRouter.input") - private MessageChannel scriptRouterInput; - - @Autowired - private PollableChannel longStrings; - - @Autowired - private PollableChannel shortStrings; - - @Autowired - private PollableChannel results; - - @Autowired - private PollableChannel messageSourceChannel; - - @BeforeAll - public static void setup() throws IOException { - SCRIPT_FILE = new File(FOLDER, "script.py"); - FileCopyUtils.copy("1".getBytes(), SCRIPT_FILE); - } - - @AfterEach - public void clear() { - ((QueueChannelOperations) this.results).clear(); - } - - @Test - public void splitterTest() { - this.splitterInput.send(new GenericMessage<>("x,y,z")); - assertThat(this.results.receive(10000).getPayload()).isEqualTo("x"); - assertThat(this.results.receive(10000).getPayload()).isEqualTo("y"); - assertThat(this.results.receive(10000).getPayload()).isEqualTo("z"); - } - - @Test - public void transformerTest() { - for (int i = 1; i <= 3; i++) { - this.transformerInput.send(new GenericMessage<>("test-" + i)); - } - assertThat(this.results.receive(10000).getPayload()).isEqualTo("ruby-test-1-bar"); - assertThat(this.results.receive(10000).getPayload()).isEqualTo("ruby-test-2-bar"); - assertThat(this.results.receive(10000).getPayload()).isEqualTo("ruby-test-3-bar"); - } - - @Test - public void filterTest() { - Message message1 = MessageBuilder.withPayload("bad").setHeader("type", "bad").build(); - Message message2 = MessageBuilder.withPayload("good").setHeader("type", "good").build(); - this.filterInput.send(message1); - this.filterInput.send(message2); - assertThat(this.results.receive(10000).getPayload()).isEqualTo("good"); - assertThat(this.results.receive(0)).isNull(); - assertThat(this.discardChannel.receive(10000).getPayload()).isEqualTo("bad"); - assertThat(this.discardChannel.receive(0)).isNull(); - } - - @Test - public void serviceWithRefreshCheckDelayTest() throws IOException { - this.scriptServiceInput.send(new GenericMessage("test")); - assertThat(this.results.receive(10000).getPayload()).isEqualTo(1); - - FileCopyUtils.copy("2".getBytes(), SCRIPT_FILE); - SCRIPT_FILE.setLastModified(System.currentTimeMillis() + 10000); // force refresh - - this.scriptServiceInput.send(new GenericMessage("test")); - assertThat(this.results.receive(10000).getPayload()).isEqualTo(2); - } - - @Test - public void routerTest() { - this.scriptRouterInput.send(new GenericMessage<>("aardvark")); - this.scriptRouterInput.send(new GenericMessage<>("bear")); - this.scriptRouterInput.send(new GenericMessage<>("cat")); - this.scriptRouterInput.send(new GenericMessage<>("dog")); - this.scriptRouterInput.send(new GenericMessage<>("elephant")); - assertThat(this.shortStrings.receive(10000).getPayload()).isEqualTo("bear"); - assertThat(this.shortStrings.receive(10000).getPayload()).isEqualTo("cat"); - assertThat(this.shortStrings.receive(10000).getPayload()).isEqualTo("dog"); - assertThat(this.longStrings.receive(10000).getPayload()).isEqualTo("aardvark"); - assertThat(this.longStrings.receive(10000).getPayload()).isEqualTo("elephant"); - } - - @Test - public void messageSourceTest() throws InterruptedException { - Message message = this.messageSourceChannel.receive(20000); - assertThat(message).isNotNull(); - Object payload = message.getPayload(); - assertThat(payload).isInstanceOf(Date.class); - - // Some time window to avoid dates collision - Thread.sleep(2); - - assertThat(((Date) payload).before(new Date())).isTrue(); - - assertThat(this.messageSourceChannel.receive(20000)).isNotNull(); - } - - @Autowired - @Qualifier("kotlinScriptFlow.input") - private MessageChannel kotlinScriptFlowInput; - - @Test - public void testKotlinScript() { - this.kotlinScriptFlowInput.send(new GenericMessage<>(3)); - Message receive = this.results.receive(10_000); - assertThat(receive).isNotNull() - .extracting(Message::getPayload) - .isEqualTo(5); - } - - @Configuration - @EnableIntegration - public static class ContextConfiguration { - - @Value("scripts/TesSplitterScript.groovy") - private Resource splitterScript; - - @Value("scripts/TestFilterScript.kts") - private Resource filterScript; - - @Bean - public PollableChannel results() { - return new QueueChannel(); - } - - @Bean - public IntegrationFlow scriptSplitter() { - return f -> f.split(Scripts.processor(this.splitterScript)).channel(results()); - } - - @Bean - public IntegrationFlow scriptTransformer() { - return f -> f - .transform(Scripts.processor("scripts/TestTransformerScript.rb") - .lang("ruby") - .variable("foo", "bar")) - .channel(results()); - } - - @Bean - public PollableChannel discardChannel() { - return new QueueChannel(); - } - - @Bean - public IntegrationFlow scriptFilter() { - return f -> f.filter(Scripts.processor(this.filterScript), e -> e.discardChannel("discardChannel")) - .channel(results()); - } - - @Bean - public IntegrationFlow scriptService() { - return f -> f.handle(Scripts.processor("file:" + SCRIPT_FILE.getAbsolutePath()).refreshCheckDelay(0)) - .channel(results()); - } - - @Bean - public IntegrationFlow scriptRouter() { - return f -> f.route(Scripts.processor("scripts/TestRouterScript.py")); - } - - @Bean - public PollableChannel longStrings() { - return new QueueChannel(); - } - - @Bean - public PollableChannel shortStrings() { - return new QueueChannel(); - } - - @Bean - public IntegrationFlow scriptPollingAdapter() { - return IntegrationFlow - .from(Scripts.messageSource("scripts/TestMessageSourceScript.rb"), - e -> e.poller(p -> p.fixedDelay(100))) - .channel(c -> c.queue("messageSourceChannel")) - .get(); - } - - @Bean - public IntegrationFlow kotlinScriptFlow() { - return f -> f - .handle(Scripts.processor(new ByteArrayResource("2 + bindings[\"payload\"] as Int".getBytes())) - .lang("kotlin")) - .channel(results()); - } - - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-context.xml deleted file mode 100644 index 7d0ab50cd9e..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-context.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-fail1-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-fail1-context.xml deleted file mode 100644 index 44e3aceedd0..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-fail1-context.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-fail2-context.xml b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-fail2-context.xml deleted file mode 100644 index be2d5d52ffe..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests-fail2-context.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests.java deleted file mode 100644 index 286168b76d8..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/DeriveLanguageFromExtensionTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.scripting.PolyglotScriptExecutor; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author David Turanski - * @author Artem Bilan - * - */ -@SpringJUnitConfig -@DirtiesContext -public class DeriveLanguageFromExtensionTests { - - @Autowired - private ApplicationContext ctx; - - @ParameterizedTest - @MethodSource("languageExecutorSource") - public void testParseLanguage(String language, Class executorClass, ArgumentsAccessor argumentsAccessor) { - assertThat(this.ctx.getBeansOfType(ScriptExecutingMessageProcessor.class)).hasSize(5); - - var processor = - ctx.getBean( - "org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor#" + - (argumentsAccessor.getInvocationIndex() - 1), - ScriptExecutingMessageProcessor.class); - - ScriptExecutor executor = TestUtils.getPropertyValue(processor, "scriptExecutor", ScriptExecutor.class); - if (executor instanceof PolyglotScriptExecutor) { - assertThat(TestUtils.getPropertyValue(executor, "language")).isEqualTo(language); - } - else { - AbstractScriptExecutor abstractScriptExecutor = (AbstractScriptExecutor) executor; - assertThat(abstractScriptExecutor.getScriptEngine().getFactory().getLanguageName()).isEqualTo(language); - } - assertThat(executor.getClass()).isEqualTo(executorClass); - } - - @Test - public void testBadExtension() { - assertThatExceptionOfType(BeanDefinitionStoreException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-fail1-context.xml", - getClass()).close()) - .withStackTraceContaining("No suitable scripting engine found for extension 'xx'"); - } - - @Test - public void testNoExtension() { - assertThatExceptionOfType(BeanDefinitionStoreException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-fail2-context.xml", - getClass()).close()) - .withStackTraceContaining("Unable to determine language for script 'foo'"); - } - - private static Stream languageExecutorSource() { - return Stream.of( - Arguments.of("ruby", RubyScriptExecutor.class), - Arguments.of("Groovy", DefaultScriptExecutor.class), - Arguments.of("python", PolyglotScriptExecutor.class), - Arguments.of("kotlin", DefaultScriptExecutor.class), - Arguments.of("js", PolyglotScriptExecutor.class)); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutingMessageProcessorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutingMessageProcessorTests.java deleted file mode 100644 index 48e4962609a..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutingMessageProcessorTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.io.ClassPathResource; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.scripting.ScriptSource; -import org.springframework.scripting.support.ResourceScriptSource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author David Turanski - * @author Artem Bilan - * - */ -public class Jsr223ScriptExecutingMessageProcessorTests { - - private static final ScriptExecutor SCRIPT_EXECUTOR = ScriptExecutorFactory.getScriptExecutor("jruby"); - - @Test - public void testExecuteWithVariables() { - Map vars = new HashMap<>(); - vars.put("one", 1); - vars.put("two", "two"); - vars.put("three", 3); - - ScriptSource scriptSource = - new ResourceScriptSource( - new ClassPathResource("/org/springframework/integration/scripting/jsr223/print_message.rb")); - - ScriptExecutingMessageProcessor messageProcessor = - new ScriptExecutingMessageProcessor(scriptSource, SCRIPT_EXECUTOR, vars); - messageProcessor.setBeanFactory(Mockito.mock(BeanFactory.class)); - - Message message = new GenericMessage<>("hello"); - - Object obj = messageProcessor.processMessage(message); - - assertThat(obj.toString()).contains("hello modified 1 two 3"); - } - - @Test - public void testWithNoVars() { - ScriptSource scriptSource = - new ResourceScriptSource( - new ClassPathResource("/org/springframework/integration/scripting/jsr223/print_message.rb")); - - ScriptExecutingMessageProcessor messageProcessor = - new ScriptExecutingMessageProcessor(scriptSource, SCRIPT_EXECUTOR); - messageProcessor.setBeanFactory(Mockito.mock(BeanFactory.class)); - - Message message = new GenericMessage<>("hello"); - - Object obj = messageProcessor.processMessage(message); - - assertThat(obj.toString()).contains("hello modified"); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java deleted file mode 100644 index 88e4e2ce265..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.scripting.support.ResourceScriptSource; -import org.springframework.scripting.support.StaticScriptSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * @author David Turanski - * @author Artem Bilan - */ -public class Jsr223ScriptExecutorTests { - - @Test - public void test() { - ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("jruby"); - executor.executeScript(new StaticScriptSource("'hello, world'")); - executor.executeScript(new StaticScriptSource("'hello, again'")); - - Map variables = new HashMap<>(); - - Map headers = new HashMap<>(); - headers.put("one", 1); - headers.put("two", "two"); - headers.put("three", 3); - - variables.put("payload", "payload"); - variables.put("headers", headers); - - Resource resource = new ClassPathResource("/org/springframework/integration/scripting/jsr223/print_message.rb"); - String result = (String) executor.executeScript(new ResourceScriptSource(resource), variables); - - assertThat(result).isNotNull().contains("payload modified"); - } - - @Test - public void testPython() { - ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("python"); - Object obj = executor.executeScript(new StaticScriptSource("x=2")); - assertThat(obj).isEqualTo(2); - - obj = executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)")); - assertThat(obj).isEqualTo(2); - } - - @Test - public void testInvalidLanguageThrowsIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> ScriptExecutorFactory.getScriptExecutor("foo")); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java deleted file mode 100644 index 14a93e93658..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.scripting.jsr223; - -import java.util.HashMap; -import java.util.Map; - -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.integration.scripting.PolyglotScriptExecutor; -import org.springframework.integration.scripting.ScriptExecutor; -import org.springframework.scripting.ScriptSource; -import org.springframework.scripting.support.ResourceScriptSource; -import org.springframework.scripting.support.StaticScriptSource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author David Turanski - * @author Gary Russell - * @author Artem Bilan - * - */ -public class PythonScriptExecutorTests { - - private ScriptExecutor executor; - - @BeforeEach - public void init() { - this.executor = new PolyglotScriptExecutor("python"); - } - - @Test - public void testLiteral() { - Object obj = this.executor.executeScript(new StaticScriptSource("3+4")); - assertThat(obj).isEqualTo(7); - - obj = this.executor.executeScript(new StaticScriptSource("'hello,world'")); - assertThat(obj).isEqualTo("hello,world"); - } - - @Test - - public void test1() { - Object obj = this.executor.executeScript(new StaticScriptSource("x=2")); - assertThat(obj).isEqualTo(2); - } - - @Test - public void test2() { - Object obj = this.executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)")); - assertThat(obj).isEqualTo(2); - } - - @Test - public void test3() { - ScriptSource source = - new ResourceScriptSource( - new ClassPathResource("/org/springframework/integration/scripting/jsr223/test3.py")); - Object obj = this.executor.executeScript(source); - assertThat(obj) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .containsOnly(1, 2, 3); - } - - @Test - public void test3WithVariables() { - ScriptSource source = - new ResourceScriptSource( - new ClassPathResource("/org/springframework/integration/scripting/jsr223/test3.py")); - HashMap variables = new HashMap<>(); - variables.put("foo", "bar"); - Object obj = this.executor.executeScript(source, variables); - assertThat(obj) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .containsOnly(1, 2, 3); - } - - @Test - public void testEmbeddedVariable() { - Map variables = new HashMap<>(); - variables.put("scope", "world"); - Object obj = this.executor.executeScript(new StaticScriptSource("\"hello, %s\"% scope"), variables); - assertThat(obj).isEqualTo("hello, world"); - } - -} diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/hello.rb b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/hello.rb deleted file mode 100755 index a262a2c207d..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/hello.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'java' - -class RubyHello - def say - "hello,world" - end -end -RubyHello.new diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/print_message.rb b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/print_message.rb deleted file mode 100755 index 41fb1007d0c..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/print_message.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "java" -java_import 'java.util.Date' -#payload and headers a global variable -if payload - payload = payload+" modified #{(defined? one) ? one : ''} #{(defined? two) ? two : ''} #{(defined? three) ? three : ''} #{Date.new}" -end -if headers - headers.each {|key, value| puts "#{key} is #{value}" } -end -puts payload -payload diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/test3.py b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/test3.py deleted file mode 100644 index 3e4527109b5..00000000000 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/test3.py +++ /dev/null @@ -1,6 +0,0 @@ -def foo(): - return(1,2,3) - - -(x,y,z) = foo() -result = (x,y,z) diff --git a/spring-integration-scripting/src/test/resources/log4j2-test.xml b/spring-integration-scripting/src/test/resources/log4j2-test.xml deleted file mode 100644 index 8481f076361..00000000000 --- a/spring-integration-scripting/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/spring-integration-scripting/src/test/resources/scripts/TesSplitterScript.groovy b/spring-integration-scripting/src/test/resources/scripts/TesSplitterScript.groovy deleted file mode 100644 index 803d916e18c..00000000000 --- a/spring-integration-scripting/src/test/resources/scripts/TesSplitterScript.groovy +++ /dev/null @@ -1 +0,0 @@ -payload.split(',') diff --git a/spring-integration-scripting/src/test/resources/scripts/TestFilterScript.kts b/spring-integration-scripting/src/test/resources/scripts/TestFilterScript.kts deleted file mode 100644 index 35189725d46..00000000000 --- a/spring-integration-scripting/src/test/resources/scripts/TestFilterScript.kts +++ /dev/null @@ -1 +0,0 @@ -(bindings["headers"] as Map)["type"] == "good" diff --git a/spring-integration-scripting/src/test/resources/scripts/TestMessageSourceScript.rb b/spring-integration-scripting/src/test/resources/scripts/TestMessageSourceScript.rb deleted file mode 100644 index 5f532006ce3..00000000000 --- a/spring-integration-scripting/src/test/resources/scripts/TestMessageSourceScript.rb +++ /dev/null @@ -1 +0,0 @@ -java.util.Date.new diff --git a/spring-integration-scripting/src/test/resources/scripts/TestRouterScript.py b/spring-integration-scripting/src/test/resources/scripts/TestRouterScript.py deleted file mode 100644 index 0a9f4866160..00000000000 --- a/spring-integration-scripting/src/test/resources/scripts/TestRouterScript.py +++ /dev/null @@ -1 +0,0 @@ -'longStrings' if len(payload) > 5 else 'shortStrings' \ No newline at end of file diff --git a/spring-integration-scripting/src/test/resources/scripts/TestTransformerScript.rb b/spring-integration-scripting/src/test/resources/scripts/TestTransformerScript.rb deleted file mode 100644 index e6ed08edeb0..00000000000 --- a/spring-integration-scripting/src/test/resources/scripts/TestTransformerScript.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Transformer - def transform(payload, arg) - "ruby-#{payload}-#{arg}" - end -end - -Transformer.new.transform(payload, foo) diff --git a/spring-integration-sftp/.gitignore b/spring-integration-sftp/.gitignore deleted file mode 100644 index 78d35dfffdb..00000000000 --- a/spring-integration-sftp/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -local-test-dir/*.test -local-test-dir/rollback -remote-target-dir/*foo* -remote-target-dir/*test* diff --git a/spring-integration-sftp/local-test-dir/readme.txt b/spring-integration-sftp/local-test-dir/readme.txt deleted file mode 100644 index e308f5e4eed..00000000000 --- a/spring-integration-sftp/local-test-dir/readme.txt +++ /dev/null @@ -1 +0,0 @@ -don't delete this dir used for testing \ No newline at end of file diff --git a/spring-integration-sftp/remote-target-dir/readme.txt b/spring-integration-sftp/remote-target-dir/readme.txt deleted file mode 100644 index e308f5e4eed..00000000000 --- a/spring-integration-sftp/remote-target-dir/readme.txt +++ /dev/null @@ -1 +0,0 @@ -don't delete this dir used for testing \ No newline at end of file diff --git a/spring-integration-sftp/remote-test-dir/a.test b/spring-integration-sftp/remote-test-dir/a.test deleted file mode 100644 index 5ab2f8a4323..00000000000 --- a/spring-integration-sftp/remote-test-dir/a.test +++ /dev/null @@ -1 +0,0 @@ -Hello \ No newline at end of file diff --git a/spring-integration-sftp/remote-test-dir/b.test b/spring-integration-sftp/remote-test-dir/b.test deleted file mode 100644 index e9029c7668d..00000000000 --- a/spring-integration-sftp/remote-test-dir/b.test +++ /dev/null @@ -1 +0,0 @@ -Bye \ No newline at end of file diff --git a/spring-integration-sftp/remote-test-dir/readme.txt b/spring-integration-sftp/remote-test-dir/readme.txt deleted file mode 100644 index c94b0e60d5e..00000000000 --- a/spring-integration-sftp/remote-test-dir/readme.txt +++ /dev/null @@ -1 +0,0 @@ -Don't delete. This directory is used by test cases \ No newline at end of file diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpInboundChannelAdapterParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpInboundChannelAdapterParser.java deleted file mode 100644 index 86029eabd56..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpInboundChannelAdapterParser.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.springframework.integration.file.config.AbstractRemoteFileInboundChannelAdapterParser; -import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter; -import org.springframework.integration.file.filters.FileListFilter; -import org.springframework.integration.file.remote.synchronizer.InboundFileSynchronizer; -import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; -import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; -import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; -import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer; -import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource; - -/** - * Parser for 'sftp:inbound-channel-adapter' - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -public class SftpInboundChannelAdapterParser extends AbstractRemoteFileInboundChannelAdapterParser { - - @Override - protected String getMessageSourceClassname() { - return SftpInboundFileSynchronizingMessageSource.class.getName(); - } - - @Override - protected Class getInboundFileSynchronizerClass() { - return SftpInboundFileSynchronizer.class; - } - - @Override - protected Class> getSimplePatternFileListFilterClass() { - return SftpSimplePatternFileListFilter.class; - } - - @Override - protected Class> getRegexPatternFileListFilterClass() { - return SftpRegexPatternFileListFilter.class; - } - - @Override - protected Class> getPersistentAcceptOnceFileListFilterClass() { - return SftpPersistentAcceptOnceFileListFilter.class; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java deleted file mode 100644 index 76932654b7b..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; - -/** - * Provides namespace support for using SFTP. - * This is largely based on the FTP support by Iwein Fuld. - * - * @author Josh Long - * @author Mark Fisher - * @author Oleg ZHurakousky - * @since 2.0 - */ -public class SftpNamespaceHandler extends AbstractIntegrationNamespaceHandler { - - @Override - public void init() { - registerBeanDefinitionParser("inbound-channel-adapter", new SftpInboundChannelAdapterParser()); - registerBeanDefinitionParser("inbound-streaming-channel-adapter", new SftpStreamingInboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-channel-adapter", new SftpOutboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-gateway", new SftpOutboundGatewayParser()); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java deleted file mode 100644 index cdee95b019b..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser; -import org.springframework.integration.file.remote.RemoteFileOperations; -import org.springframework.integration.sftp.outbound.SftpMessageHandler; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * Parser for SFTP Outbound Channel Adapters. - * - * @author Gary Russell - * @since 4.1 - * - */ -public class SftpOutboundChannelAdapterParser extends RemoteFileOutboundChannelAdapterParser { - - @Override - protected Class handlerClass() { - return SftpMessageHandler.class; - } - - @Override - protected Class> getTemplateClass() { - return SftpRemoteFileTemplate.class; - } - - @Override - protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) { - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal"); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java deleted file mode 100644 index 5e9b643c6e8..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.w3c.dom.Element; - -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser; -import org.springframework.integration.file.remote.RemoteFileOperations; -import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; -import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; -import org.springframework.integration.sftp.gateway.SftpOutboundGateway; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * @author Gary Russell - * @author Artem Bilan - * @since 2.1 - * - */ -public class SftpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayParser { - - @Override - public String getGatewayClassName() { - return SftpOutboundGateway.class.getName(); - } - - @Override - protected String getSimplePatternFileListFilterClassName() { - return SftpSimplePatternFileListFilter.class.getName(); - } - - @Override - protected String getRegexPatternFileListFilterClassName() { - return SftpRegexPatternFileListFilter.class.getName(); - } - - @Override - protected Class> getTemplateClass() { - return SftpRemoteFileTemplate.class; - } - - @Override - protected void postProcessBuilder(BeanDefinitionBuilder builder, Element element) { - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod", "chmodOctal"); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpStreamingInboundChannelAdapterParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpStreamingInboundChannelAdapterParser.java deleted file mode 100644 index 5a4b039d429..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpStreamingInboundChannelAdapterParser.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.springframework.integration.core.MessageSource; -import org.springframework.integration.file.config.AbstractRemoteFileStreamingInboundChannelAdapterParser; -import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter; -import org.springframework.integration.file.filters.FileListFilter; -import org.springframework.integration.file.remote.RemoteFileOperations; -import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; -import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; -import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; -import org.springframework.integration.sftp.inbound.SftpStreamingMessageSource; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -public class SftpStreamingInboundChannelAdapterParser extends AbstractRemoteFileStreamingInboundChannelAdapterParser { - - @Override - protected Class> getTemplateClass() { - return SftpRemoteFileTemplate.class; - } - - @Override - protected Class> getMessageSourceClass() { - return SftpStreamingMessageSource.class; - } - - @Override - protected Class> getSimplePatternFileListFilterClass() { - return SftpSimplePatternFileListFilter.class; - } - - @Override - protected Class> getRegexPatternFileListFilterClass() { - return SftpRegexPatternFileListFilter.class; - } - - @Override - protected Class> getPersistentAcceptOnceFileListFilterClass() { - return SftpPersistentAcceptOnceFileListFilter.class; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/package-info.java deleted file mode 100644 index 15977fa1902..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for configuration - parsers, namespace handlers. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.config; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/Sftp.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/Sftp.java deleted file mode 100644 index a37351f2b35..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/Sftp.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.dsl; - -import java.io.File; -import java.util.Comparator; - -import org.apache.sshd.sftp.client.SftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.MessageSessionCallback; -import org.springframework.integration.file.remote.RemoteFileTemplate; -import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.file.support.FileExistsMode; -import org.springframework.integration.sftp.gateway.SftpOutboundGateway; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * The factory for SFTP components. - * - * @author Artem Bilan - * @author Gary Russell - * @author Deepak Gunasekaran - * - * @since 5.0 - */ -public final class Sftp { - - /** - * An {@link SftpInboundChannelAdapterSpec} factory for an inbound channel adapter spec. - * @param sessionFactory the session factory. - * @return the spec. - */ - public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory sessionFactory) { - return inboundAdapter(sessionFactory, null); - } - - /** - * An {@link SftpInboundChannelAdapterSpec} factory for an inbound channel adapter spec. - * @param sessionFactory the session factory. - * @param receptionOrderComparator the comparator. - * @return the spec. - */ - public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory sessionFactory, - @Nullable Comparator receptionOrderComparator) { - - return new SftpInboundChannelAdapterSpec(sessionFactory, receptionOrderComparator); - } - - /** - * An {@link SftpStreamingInboundChannelAdapterSpec} factory for an inbound channel - * adapter spec. - * @param remoteFileTemplate the remote file template. - * @return the spec. - */ - public static SftpStreamingInboundChannelAdapterSpec inboundStreamingAdapter( - RemoteFileTemplate remoteFileTemplate) { - - return inboundStreamingAdapter(remoteFileTemplate, null); - } - - /** - * An {@link SftpStreamingInboundChannelAdapterSpec} factory for an inbound channel - * adapter spec. - * @param remoteFileTemplate the remote file template. - * @param receptionOrderComparator the comparator. - * @return the spec. - */ - public static SftpStreamingInboundChannelAdapterSpec inboundStreamingAdapter( - RemoteFileTemplate remoteFileTemplate, - @Nullable Comparator receptionOrderComparator) { - - return new SftpStreamingInboundChannelAdapterSpec(remoteFileTemplate, receptionOrderComparator); - } - - /** - * An {@link SftpMessageHandlerSpec} factory for an outbound channel adapter spec. - * @param sessionFactory the session factory. - * @return the spec. - */ - public static SftpMessageHandlerSpec outboundAdapter(SessionFactory sessionFactory) { - return new SftpMessageHandlerSpec(sessionFactory); - } - - /** - * An {@link SftpMessageHandlerSpec} factory for an outbound channel adapter spec. - * @param sessionFactory the session factory. - * @param fileExistsMode the file exists mode. - * @return the spec. - */ - public static SftpMessageHandlerSpec outboundAdapter(SessionFactory sessionFactory, - FileExistsMode fileExistsMode) { - - return outboundAdapter(new SftpRemoteFileTemplate(sessionFactory), fileExistsMode); - } - - /** - * An {@link SftpMessageHandlerSpec} factory for an outbound channel adapter spec. - * @param sftpRemoteFileTemplate the remote file template. - * @return the spec. - * @since 5.4 - */ - public static SftpMessageHandlerSpec outboundAdapter(SftpRemoteFileTemplate sftpRemoteFileTemplate) { - return new SftpMessageHandlerSpec(sftpRemoteFileTemplate); - } - - /** - * An {@link SftpMessageHandlerSpec} factory for an outbound channel adapter spec. - * @param sftpRemoteFileTemplate the remote file template. - * @param fileExistsMode the file exists mode. - * @return the spec. - * @since 5.4 - */ - public static SftpMessageHandlerSpec outboundAdapter(SftpRemoteFileTemplate sftpRemoteFileTemplate, - FileExistsMode fileExistsMode) { - - return new SftpMessageHandlerSpec(sftpRemoteFileTemplate, fileExistsMode); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link SessionFactory}, - * {@link AbstractRemoteFileOutboundGateway.Command}. - * @param sessionFactory the {@link SessionFactory}. - * @param command the command to perform on the FTP. - * @return the {@link SftpOutboundGatewaySpec} - * @since 6.4 - */ - public static SftpOutboundGatewaySpec outboundGateway(SessionFactory sessionFactory, - AbstractRemoteFileOutboundGateway.Command command) { - - return outboundGateway(sessionFactory, command, null); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link SessionFactory}, - * {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the - * remoteFilePath. - * @param sessionFactory the {@link SessionFactory}. - * @param command the command to perform on the FTP. - * @param expression the remoteFilePath SpEL expression. - * @return the {@link SftpOutboundGatewaySpec} - */ - public static SftpOutboundGatewaySpec outboundGateway(SessionFactory sessionFactory, - AbstractRemoteFileOutboundGateway.Command command, @Nullable String expression) { - - return outboundGateway(sessionFactory, command.getCommand(), expression); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link SessionFactory}, - * {@link AbstractRemoteFileOutboundGateway.Command}. - * @param sessionFactory the {@link SessionFactory}. - * @param command the command to perform on the FTP. - * @return the {@link SftpOutboundGatewaySpec} - * @since 6.4 - * @see RemoteFileTemplate - */ - public static SftpOutboundGatewaySpec outboundGateway(SessionFactory sessionFactory, - String command) { - - return outboundGateway(sessionFactory, command, null); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link SessionFactory}, - * {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the - * remoteFilePath. - * @param sessionFactory the {@link SessionFactory}. - * @param command the command to perform on the FTP. - * @param expression the remoteFilePath SpEL expression. - * @return the {@link SftpOutboundGatewaySpec} - * @see RemoteFileTemplate - */ - public static SftpOutboundGatewaySpec outboundGateway(SessionFactory sessionFactory, - String command, @Nullable String expression) { - - return new SftpOutboundGatewaySpec(new SftpOutboundGateway(sessionFactory, command, expression)); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link RemoteFileTemplate}, - * {@link AbstractRemoteFileOutboundGateway.Command}. - * @param remoteFileTemplate the {@link RemoteFileTemplate} to be based on. - * @param command the command to perform on the SFTP. - * @return the {@link SftpOutboundGatewaySpec} - * @since 6.4 - * @see RemoteFileTemplate - */ - public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate remoteFileTemplate, - AbstractRemoteFileOutboundGateway.Command command) { - - return outboundGateway(remoteFileTemplate, command, null); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link RemoteFileTemplate}, - * {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the remote path. - * @param remoteFileTemplate the {@link RemoteFileTemplate} to be based on. - * @param command the command to perform on the SFTP. - * @param expression the remote path SpEL expression. - * @return the {@link SftpOutboundGatewaySpec} - * @see RemoteFileTemplate - */ - public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate remoteFileTemplate, - AbstractRemoteFileOutboundGateway.Command command, @Nullable String expression) { - - return outboundGateway(remoteFileTemplate, command.getCommand(), expression); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link RemoteFileTemplate}, - * {@link AbstractRemoteFileOutboundGateway.Command}. - * @param remoteFileTemplate the {@link RemoteFileTemplate} to be based on. - * @param command the command to perform on the SFTP. - * @return the {@link SftpOutboundGatewaySpec} - * @since 6.4 - * @see RemoteFileTemplate - */ - public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate remoteFileTemplate, - String command) { - - return outboundGateway(remoteFileTemplate, command, null); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link RemoteFileTemplate}, - * {@link AbstractRemoteFileOutboundGateway.Command} and {@code expression} for the remoteFilePath. - * @param remoteFileTemplate the {@link RemoteFileTemplate} to be based on. - * @param command the command to perform on the SFTP. - * @param expression the remoteFilePath SpEL expression. - * @return the {@link SftpOutboundGatewaySpec} - * @see RemoteFileTemplate - */ - public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate remoteFileTemplate, - String command, @Nullable String expression) { - - return new SftpOutboundGatewaySpec(new SftpOutboundGateway(remoteFileTemplate, command, expression)); - } - - /** - * Produce a {@link SftpOutboundGatewaySpec} based on the {@link MessageSessionCallback}. - * @param sessionFactory the {@link SessionFactory} to connect to. - * @param messageSessionCallback the {@link MessageSessionCallback} to perform SFTP operation(s) - * with the {@code Message} context. - * @return the {@link SftpOutboundGatewaySpec} - * @see MessageSessionCallback - */ - public static SftpOutboundGatewaySpec outboundGateway(SessionFactory sessionFactory, - MessageSessionCallback messageSessionCallback) { - - return new SftpOutboundGatewaySpec(new SftpOutboundGateway(sessionFactory, messageSessionCallback)); - } - - private Sftp() { - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpInboundChannelAdapterSpec.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpInboundChannelAdapterSpec.java deleted file mode 100644 index 6e9cda24e8d..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpInboundChannelAdapterSpec.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.dsl; - -import java.io.File; -import java.util.Comparator; - -import org.apache.sshd.sftp.client.SftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.dsl.RemoteFileInboundChannelAdapterSpec; -import org.springframework.integration.file.filters.CompositeFileListFilter; -import org.springframework.integration.file.filters.FileListFilter; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.metadata.SimpleMetadataStore; -import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; -import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; -import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; -import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer; -import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource; - -/** - * A {@link RemoteFileInboundChannelAdapterSpec} for an {@link SftpInboundFileSynchronizingMessageSource}. - * - * @author Artem Bilan - * - * @since 5.0 - */ -public class SftpInboundChannelAdapterSpec - extends RemoteFileInboundChannelAdapterSpec { - - protected SftpInboundChannelAdapterSpec(SessionFactory sessionFactory, - @Nullable Comparator comparator) { - - super(new SftpInboundFileSynchronizer(sessionFactory)); - this.target = new SftpInboundFileSynchronizingMessageSource(this.synchronizer, comparator); - } - - /** - * @param pattern the Ant style pattern filter to use. - * @see SftpSimplePatternFileListFilter - * @see #filter(org.springframework.integration.file.filters.FileListFilter) - */ - @Override - public SftpInboundChannelAdapterSpec patternFilter(String pattern) { - return filter(composeFilters(new SftpSimplePatternFileListFilter(pattern))); - } - - /** - * @param regex the RegExp pattern to use. - * @see SftpRegexPatternFileListFilter - * @see #filter(org.springframework.integration.file.filters.FileListFilter) - */ - @Override - public SftpInboundChannelAdapterSpec regexFilter(String regex) { - return filter(composeFilters(new SftpRegexPatternFileListFilter(regex))); - } - - private CompositeFileListFilter composeFilters(FileListFilter - fileListFilter) { - - CompositeFileListFilter compositeFileListFilter = new CompositeFileListFilter<>(); - compositeFileListFilter.addFilters(fileListFilter, - new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpMessageSource")); - return compositeFileListFilter; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpMessageHandlerSpec.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpMessageHandlerSpec.java deleted file mode 100644 index 44f43bedd78..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpMessageHandlerSpec.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.dsl; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.dsl.FileTransferringMessageHandlerSpec; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.file.support.FileExistsMode; -import org.springframework.integration.sftp.outbound.SftpMessageHandler; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * @author Artem Bilan - * @author Joaquin Santana - * @author Deepak Gunasekaran - * - * @since 5.0 - */ -public class SftpMessageHandlerSpec - extends FileTransferringMessageHandlerSpec { - - protected SftpMessageHandlerSpec(SessionFactory sessionFactory) { - this.target = new SftpMessageHandler(sessionFactory); - } - - protected SftpMessageHandlerSpec(SftpRemoteFileTemplate sftpRemoteFileTemplate) { - this.target = new SftpMessageHandler(sftpRemoteFileTemplate); - } - - protected SftpMessageHandlerSpec(SftpRemoteFileTemplate sftpRemoteFileTemplate, FileExistsMode fileExistsMode) { - this.target = new SftpMessageHandler(sftpRemoteFileTemplate, fileExistsMode); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpOutboundGatewaySpec.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpOutboundGatewaySpec.java deleted file mode 100644 index bb1dc3ae208..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpOutboundGatewaySpec.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.dsl; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.dsl.RemoteFileOutboundGatewaySpec; -import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway; -import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; -import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; - -/** - * @author Artem Bilan - * @author Gary Russell - * - * @since 5.0 - */ -public class SftpOutboundGatewaySpec - extends RemoteFileOutboundGatewaySpec { - - protected SftpOutboundGatewaySpec(AbstractRemoteFileOutboundGateway outboundGateway) { - super(outboundGateway); - } - - /** - * @see SftpSimplePatternFileListFilter - */ - @Override - public SftpOutboundGatewaySpec patternFileNameFilter(String pattern) { - return filter(new SftpSimplePatternFileListFilter(pattern)); - } - - /** - * @see SftpRegexPatternFileListFilter - */ - @Override - public SftpOutboundGatewaySpec regexFileNameFilter(String regex) { - return filter(new SftpRegexPatternFileListFilter(regex)); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpStreamingInboundChannelAdapterSpec.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpStreamingInboundChannelAdapterSpec.java deleted file mode 100644 index d581d7c8a23..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/SftpStreamingInboundChannelAdapterSpec.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.dsl; - -import java.util.Comparator; - -import org.apache.sshd.sftp.client.SftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.dsl.RemoteFileStreamingInboundChannelAdapterSpec; -import org.springframework.integration.file.filters.CompositeFileListFilter; -import org.springframework.integration.file.filters.FileListFilter; -import org.springframework.integration.file.remote.RemoteFileTemplate; -import org.springframework.integration.metadata.SimpleMetadataStore; -import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; -import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; -import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; -import org.springframework.integration.sftp.inbound.SftpStreamingMessageSource; - -/** - * @author Gary Russell - * - * @since 5.0 - * - */ -public class SftpStreamingInboundChannelAdapterSpec - extends RemoteFileStreamingInboundChannelAdapterSpec { - - protected SftpStreamingInboundChannelAdapterSpec(RemoteFileTemplate remoteFileTemplate, - @Nullable Comparator comparator) { - - this.target = new SftpStreamingMessageSource(remoteFileTemplate, comparator); - } - - /** - * Specify a simple pattern to match remote files (e.g. '*.txt'). - * @param pattern the pattern. - * @see SftpSimplePatternFileListFilter - * @see #filter(org.springframework.integration.file.filters.FileListFilter) - */ - @Override - public SftpStreamingInboundChannelAdapterSpec patternFilter(String pattern) { - return filter(composeFilters(new SftpSimplePatternFileListFilter(pattern))); - } - - /** - * Specify a regular expression to match remote files (e.g. '[0-9].*.txt'). - * @param regex the expression. - * @see SftpRegexPatternFileListFilter - * @see #filter(org.springframework.integration.file.filters.FileListFilter) - */ - @Override - public SftpStreamingInboundChannelAdapterSpec regexFilter(String regex) { - return filter(composeFilters(new SftpRegexPatternFileListFilter(regex))); - } - - private CompositeFileListFilter composeFilters( - FileListFilter fileListFilter) { - - CompositeFileListFilter compositeFileListFilter = new CompositeFileListFilter<>(); - compositeFileListFilter.addFilters(fileListFilter, - new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpStreamingMessageSource")); - return compositeFileListFilter; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/package-info.java deleted file mode 100644 index a9d344d123a..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/dsl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides SFTP Components for the Java DSL. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.dsl; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpLastModifiedFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpLastModifiedFileListFilter.java deleted file mode 100644 index 75bb9092c33..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpLastModifiedFileListFilter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.filters; - -import java.nio.file.attribute.FileTime; -import java.time.Duration; -import java.time.Instant; -import java.util.function.Consumer; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.filters.AbstractLastModifiedFileListFilter; - -/** - * The {@link AbstractLastModifiedFileListFilter} implementation to filter those files which - * {@link FileTime#toInstant()} is less than the age in comparison with the {@link Instant#now()}. - * When discardCallback {@link #addDiscardCallback(Consumer)} is provided, it called for all the rejected files. - * - * @author Adama Sorho - * @author Artem Bilan - * - * @since 6.2 - */ -public class SftpLastModifiedFileListFilter extends AbstractLastModifiedFileListFilter { - - public SftpLastModifiedFileListFilter() { - super(); - } - - /** - * Construct a {@link SftpLastModifiedFileListFilter} instance with provided age. - * Defaults to 60 seconds. - * @param age the age in seconds. - */ - public SftpLastModifiedFileListFilter(long age) { - this(Duration.ofSeconds(age)); - } - - /** - * Construct a {@link SftpLastModifiedFileListFilter} instance with provided age. - * Defaults to 60 seconds. - * @param age the Duration - */ - public SftpLastModifiedFileListFilter(Duration age) { - super(age); - } - - @Override - protected Instant getLastModified(SftpClient.DirEntry remoteFile) { - return remoteFile.getAttributes().getModifyTime().toInstant(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpPersistentAcceptOnceFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpPersistentAcceptOnceFileListFilter.java deleted file mode 100644 index b86dd7bf8d6..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpPersistentAcceptOnceFileListFilter.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.filters; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter; -import org.springframework.integration.metadata.ConcurrentMetadataStore; - -/** - * Persistent file list filter using the server's file timestamp to detect if we've already - * 'seen' this file. - * - * @author Gary Russell - * @author David Liu - * @author Artem Bilan - * - * @since 3.0 - * - */ -public class SftpPersistentAcceptOnceFileListFilter - extends AbstractPersistentAcceptOnceFileListFilter { - - public SftpPersistentAcceptOnceFileListFilter(ConcurrentMetadataStore store, String prefix) { - super(store, prefix); - } - - @Override - protected long modified(SftpClient.DirEntry file) { - return file.getAttributes().getModifyTime().toMillis(); - } - - @Override - protected String fileName(SftpClient.DirEntry file) { - return file.getFilename(); - } - - @Override - protected boolean isDirectory(SftpClient.DirEntry file) { - return file.getAttributes().isDirectory(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpRecentFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpRecentFileListFilter.java deleted file mode 100644 index 3ae2977e9a0..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpRecentFileListFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2025-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.filters; - -import java.time.Duration; -import java.time.Instant; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.filters.AbstractRecentFileListFilter; - -/** - * The {@link AbstractRecentFileListFilter} implementation for SFTP protocol. - * - * @author Artem Bilan - * - * @since 6.5 - */ -public class SftpRecentFileListFilter extends AbstractRecentFileListFilter { - - public SftpRecentFileListFilter() { - super(); - } - - public SftpRecentFileListFilter(Duration age) { - super(age); - } - - @Override - protected Instant getLastModified(SftpClient.DirEntry remoteFile) { - return remoteFile.getAttributes().getModifyTime().toInstant(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpRegexPatternFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpRegexPatternFileListFilter.java deleted file mode 100644 index 3704999b993..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpRegexPatternFileListFilter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.filters; - -import java.util.regex.Pattern; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter; - -/** - * Implementation of {@link AbstractRegexPatternFileListFilter} for SFTP. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter { - - public SftpRegexPatternFileListFilter(String pattern) { - super(pattern); - } - - public SftpRegexPatternFileListFilter(Pattern pattern) { - super(pattern); - } - - @Override - protected String getFilename(SftpClient.DirEntry entry) { - return entry.getFilename(); - } - - @Override - protected boolean isDirectory(SftpClient.DirEntry file) { - return file.getAttributes().isDirectory(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpSimplePatternFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpSimplePatternFileListFilter.java deleted file mode 100644 index 302d347161e..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpSimplePatternFileListFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.filters; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter; - -/** - * Implementation of {@link AbstractSimplePatternFileListFilter} for SFTP. - * - * @author Mark Fisher - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.0 - */ -public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter { - - public SftpSimplePatternFileListFilter(String pattern) { - super(pattern); - } - - @Override - protected String getFilename(SftpClient.DirEntry entry) { - return entry.getFilename(); - } - - @Override - protected boolean isDirectory(SftpClient.DirEntry file) { - return file.getAttributes().isDirectory(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpSystemMarkerFilePresentFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpSystemMarkerFilePresentFileListFilter.java deleted file mode 100644 index 5cb08fb4671..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/SftpSystemMarkerFilePresentFileListFilter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2017-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.filters; - -import java.util.Map; -import java.util.function.Function; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.filters.AbstractMarkerFilePresentFileListFilter; -import org.springframework.integration.file.filters.FileListFilter; - -/** - * SFTP implementation of {@link AbstractMarkerFilePresentFileListFilter}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.0 - * - */ -public class SftpSystemMarkerFilePresentFileListFilter - extends AbstractMarkerFilePresentFileListFilter { - - public SftpSystemMarkerFilePresentFileListFilter(FileListFilter filter) { - super(filter); - } - - public SftpSystemMarkerFilePresentFileListFilter(FileListFilter filter, String suffix) { - super(filter, suffix); - } - - public SftpSystemMarkerFilePresentFileListFilter(FileListFilter filter, - Function function) { - - super(filter, function); - } - - public SftpSystemMarkerFilePresentFileListFilter( - Map, Function> filtersAndFunctions) { - - super(filtersAndFunctions); - } - - @Override - protected String getFilename(SftpClient.DirEntry file) { - return file.getFilename(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/package-info.java deleted file mode 100644 index 1c2d7cc0003..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/filters/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting SFTP file filtering. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.filters; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java deleted file mode 100644 index 2695c7f55ed..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/SftpOutboundGateway.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.gateway; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.sshd.sftp.client.SftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.AbstractFileInfo; -import org.springframework.integration.file.remote.ClientCallbackWithoutResult; -import org.springframework.integration.file.remote.MessageSessionCallback; -import org.springframework.integration.file.remote.RemoteFileOperations; -import org.springframework.integration.file.remote.RemoteFileTemplate; -import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.sftp.session.SftpFileInfo; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * Outbound Gateway for performing remote file operations via SFTP. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway { - - /** - * Construct an instance using the provided session factory and callback for - * performing operations on the session. - * @param sessionFactory the session factory. - * @param messageSessionCallback the callback. - */ - @SuppressWarnings("this-escape") - public SftpOutboundGateway(SessionFactory sessionFactory, - MessageSessionCallback messageSessionCallback) { - - this(new SftpRemoteFileTemplate(sessionFactory), messageSessionCallback); - remoteFileTemplateExplicitlySet(false); - } - - /** - * Construct an instance with the supplied remote file template and callback - * for performing operations on the session. - * @param remoteFileTemplate the remote file template. - * @param messageSessionCallback the callback. - */ - public SftpOutboundGateway(RemoteFileTemplate remoteFileTemplate, - MessageSessionCallback messageSessionCallback) { - - super(remoteFileTemplate, messageSessionCallback); - } - - /** - * Construct an instance with the supplied session factory, a command ('ls', 'get' - * etc.), and an expression to determine the remote path. - * @param sessionFactory the session factory. - * @param command the command. - * @param expression the remote path expression. - */ - @SuppressWarnings("this-escape") - public SftpOutboundGateway(SessionFactory sessionFactory, String command, - @Nullable String expression) { - - this(new SftpRemoteFileTemplate(sessionFactory), command, expression); - remoteFileTemplateExplicitlySet(false); - } - - /** - * Construct an instance with the supplied remote file template, a command ('ls', - * 'get' etc.), and an expression to determine the remote path. - * @param remoteFileTemplate the remote file template. - * @param command the command. - * @param expression the remote path expression. - */ - public SftpOutboundGateway(RemoteFileTemplate remoteFileTemplate, String command, - @Nullable String expression) { - - super(remoteFileTemplate, command, expression); - } - - @Override - protected boolean isDirectory(SftpClient.DirEntry file) { - return file.getAttributes().isDirectory(); - } - - @Override - protected boolean isLink(SftpClient.DirEntry file) { - return file.getAttributes().isSymbolicLink(); - } - - @Override - protected String getFilename(SftpClient.DirEntry file) { - return file.getFilename(); - } - - @Override - protected String getFilename(AbstractFileInfo file) { - return file.getFilename(); - } - - @Override - protected List> asFileInfoList(Collection files) { - return files.stream() - .map(SftpFileInfo::new) - .collect(Collectors.toList()); - } - - @Override - protected long getModified(SftpClient.DirEntry file) { - return file.getAttributes().getModifyTime().toMillis(); - } - - @Override - protected SftpClient.DirEntry enhanceNameWithSubDirectory(SftpClient.DirEntry file, String directory) { - return new SftpClient.DirEntry(directory + file.getFilename(), directory + file.getFilename(), - file.getAttributes()); - } - - @Override - public String getComponentType() { - return "sftp:outbound-gateway"; - } - - @Override - public boolean isChmodCapable() { - return true; - } - - @Override - protected void doChmod(RemoteFileOperations remoteFileOperations, String path, int chmod) { - remoteFileOperations.executeWithClient((ClientCallbackWithoutResult) client -> { - try { - SftpClient.Attributes attributes = client.stat(path); - attributes.setPermissions(chmod); - client.setStat(path, attributes); - } - catch (IOException ex) { - throw new UncheckedIOException( - "Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", ex); - } - }); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/package-info.java deleted file mode 100644 index c2d89a69e75..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/gateway/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting SFTP gateways. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.gateway; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java deleted file mode 100644 index 4b0f67de06f..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.inbound; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer; -import org.springframework.integration.metadata.SimpleMetadataStore; -import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; - -/** - * Handles the synchronization between a remote SFTP directory and a local mount. - * - * @author Josh Long - * @author Oleg Zhurakousky - * @author Mark Fisher - * @author Artem Bilan - * - * @since 2.0 - */ -public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer { - - /** - * Create a synchronizer with the {@code SessionFactory} used to acquire {@code Session} instances. - * @param sessionFactory The session factory. - */ - @SuppressWarnings("this-escape") - public SftpInboundFileSynchronizer(SessionFactory sessionFactory) { - super(sessionFactory); - doSetFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpMessageSource")); - } - - @Override - protected boolean isFile(SftpClient.DirEntry file) { - return file.getAttributes().isRegularFile(); - } - - @Override - protected String getFilename(SftpClient.DirEntry file) { - return file.getFilename(); - } - - @Override - protected long getModified(SftpClient.DirEntry file) { - return file.getAttributes().getModifyTime().toMillis(); - } - - @Override - protected String protocol() { - return "sftp"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizingMessageSource.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizingMessageSource.java deleted file mode 100644 index 5ab858f84eb..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizingMessageSource.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.inbound; - -import java.io.File; -import java.util.Comparator; - -import org.apache.sshd.sftp.client.SftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer; -import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource; - -/** - * A {@link org.springframework.integration.core.MessageSource} implementation for SFTP - * that delegates to an InboundFileSynchronizer. - * - * @author Josh Long - * @author Oleg Zhurakousky - * @author Artem Bilan - * - * @since 2.0 - */ -public class SftpInboundFileSynchronizingMessageSource - extends AbstractInboundFileSynchronizingMessageSource { - - public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer synchronizer) { - super(synchronizer); - } - - public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer synchronizer, - @Nullable Comparator comparator) { - - super(synchronizer, comparator); - } - - public String getComponentType() { - return "sftp:inbound-channel-adapter"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpStreamingMessageSource.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpStreamingMessageSource.java deleted file mode 100644 index 30703816b12..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpStreamingMessageSource.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.inbound; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; - -import org.apache.sshd.sftp.client.SftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.AbstractFileInfo; -import org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource; -import org.springframework.integration.file.remote.RemoteFileTemplate; -import org.springframework.integration.metadata.SimpleMetadataStore; -import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; -import org.springframework.integration.sftp.session.SftpFileInfo; - -/** - * Message source for streaming SFTP remote file contents. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessageSource { - - /** - * Construct an instance with the supplied template. - * @param template the template. - */ - public SftpStreamingMessageSource(RemoteFileTemplate template) { - this(template, null); - } - - /** - * Construct an instance with the supplied template and comparator. - * Note: the comparator is applied each time the remote directory is listed - * which only occurs when the previous list is exhausted. - * @param template the template. - * @param comparator the comparator. - */ - @SuppressWarnings("this-escape") - public SftpStreamingMessageSource(RemoteFileTemplate template, - @Nullable Comparator comparator) { - - super(template, comparator); - doSetFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpStreamingMessageSource")); - } - - @Override - public String getComponentType() { - return "sftp:inbound-streaming-channel-adapter"; - } - - @Override - protected List> asFileInfoList(Collection files) { - List> canonicalFiles = new ArrayList<>(); - for (SftpClient.DirEntry file : files) { - canonicalFiles.add(new SftpFileInfo(file)); - } - return canonicalFiles; - } - - @Override - protected boolean isDirectory(SftpClient.DirEntry file) { - return file.getAttributes().isDirectory(); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/package-info.java deleted file mode 100644 index 7c6f05491e0..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting inbound endpoints. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.inbound; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java deleted file mode 100644 index a6072a164ca..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/SftpMessageHandler.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.outbound; - -import java.io.IOException; -import java.io.UncheckedIOException; - -import org.apache.sshd.sftp.client.SftpClient; - -import org.springframework.integration.file.remote.ClientCallbackWithoutResult; -import org.springframework.integration.file.remote.RemoteFileTemplate; -import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.file.support.FileExistsMode; -import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; - -/** - * Subclass of {@link FileTransferringMessageHandler} for SFTP. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - * - */ -public class SftpMessageHandler extends FileTransferringMessageHandler { - - /** - * @param remoteFileTemplate the template. - * @see FileTransferringMessageHandler#FileTransferringMessageHandler - * (org.springframework.integration.file.remote.RemoteFileTemplate) - */ - public SftpMessageHandler(SftpRemoteFileTemplate remoteFileTemplate) { - super(remoteFileTemplate); - } - - /** - * - * @param remoteFileTemplate the template. - * @param mode the file exists mode. - * @see FileTransferringMessageHandler#FileTransferringMessageHandler - * (org.springframework.integration.file.remote.RemoteFileTemplate, FileExistsMode) - */ - public SftpMessageHandler(SftpRemoteFileTemplate remoteFileTemplate, FileExistsMode mode) { - super(remoteFileTemplate, mode); - } - - /** - * @param sessionFactory the session factory. - * @see FileTransferringMessageHandler#FileTransferringMessageHandler - * (SessionFactory) - */ - public SftpMessageHandler(SessionFactory sessionFactory) { - this(new SftpRemoteFileTemplate(sessionFactory)); - } - - @Override - public boolean isChmodCapable() { - return true; - } - - @Override - protected void doChmod(RemoteFileTemplate remoteFileTemplate, String path, int chmod) { - remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult) client -> { - try { - SftpClient.Attributes attributes = client.stat(path); - attributes.setPermissions(chmod); - client.setStat(path, attributes); - } - catch (IOException ex) { - throw new UncheckedIOException( - "Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", ex); - } - }); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java deleted file mode 100644 index da36f5f9ef1..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/outbound/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes for the SFTP outbound channel adapter. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.outbound; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/ApacheMinaSftpEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/ApacheMinaSftpEvent.java deleted file mode 100644 index d52472d1a07..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/ApacheMinaSftpEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import org.apache.sshd.server.session.ServerSession; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.server.FileServerEvent; - -/** - * {@code ApplicationEvent} generated from Apache Mina sftp events. - * - * @author Gary Russell - * @since 5.2 - * - */ -public abstract class ApacheMinaSftpEvent extends FileServerEvent { - - private static final long serialVersionUID = 1L; - - public ApacheMinaSftpEvent(Object source) { - super(source); - } - - public ApacheMinaSftpEvent(Object source, @Nullable Throwable cause) { - super(source, cause); - } - - public ServerSession getSession() { - return (ServerSession) source; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/ApacheMinaSftpEventListener.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/ApacheMinaSftpEventListener.java deleted file mode 100644 index 35232e003b2..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/ApacheMinaSftpEventListener.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import java.nio.file.CopyOption; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Map; - -import org.apache.sshd.server.session.ServerSession; -import org.apache.sshd.sftp.server.FileHandle; -import org.apache.sshd.sftp.server.SftpEventListener; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.util.Assert; - -/** - * A listener for SFTP events emitted by an Apache Mina sshd/sftp server. - * It emits selected events as Spring Framework {@code ApplicationEvent}s - * which are subclasses of {@link ApacheMinaSftpEvent}. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 5.2 - * - */ -public class ApacheMinaSftpEventListener - implements SftpEventListener, ApplicationEventPublisherAware, BeanNameAware, InitializingBean { - - @SuppressWarnings("NullAway.Init") - private ApplicationEventPublisher applicationEventPublisher; - - private @Nullable String beanName; - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - protected ApplicationEventPublisher getApplicationEventPublisher() { - return this.applicationEventPublisher; - } - - @Override - public void setBeanName(String name) { - this.beanName = name; - } - - public @Nullable String getBeanName() { - return this.beanName; - } - - @Override - public void afterPropertiesSet() { - Assert.state(this.applicationEventPublisher != null, "An ApplicationEventPublisher is required"); - } - - @Override - public void initialized(ServerSession session, int version) { - this.applicationEventPublisher.publishEvent(new SessionOpenedEvent(session, version)); - } - - @Override - public void destroying(ServerSession session) { - this.applicationEventPublisher.publishEvent(new SessionClosedEvent(session)); - } - - @Override - public void created(ServerSession session, Path path, Map attrs, Throwable thrown) { - this.applicationEventPublisher.publishEvent(new DirectoryCreatedEvent(session, path, attrs)); - } - - @Override - public void removed(ServerSession session, Path path, boolean isDirectory, Throwable thrown) { - this.applicationEventPublisher.publishEvent(new PathRemovedEvent(session, path, isDirectory, thrown)); - } - - @Override - public void written(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, byte[] data, - int dataOffset, int dataLen, Throwable thrown) { - - this.applicationEventPublisher.publishEvent(new FileWrittenEvent(session, remoteHandle, localHandle.getFile(), - dataLen, thrown)); - } - - @Override - public void moved(ServerSession session, Path srcPath, Path dstPath, Collection opts, - Throwable thrown) { - - this.applicationEventPublisher.publishEvent(new PathMovedEvent(session, srcPath, dstPath, thrown)); - } - - @Override - public String toString() { - return "ApacheMinaSftpEventListener [beanName=" + this.beanName + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/DirectoryCreatedEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/DirectoryCreatedEvent.java deleted file mode 100644 index 28639874ed2..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/DirectoryCreatedEvent.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import java.nio.file.Path; -import java.util.Map; - -/** - * An event emitted when a directory is created. - * - * @author Gary Russell - * @author Jooyoung Pyoung - * @since 5.2 - * - */ -public class DirectoryCreatedEvent extends ApacheMinaSftpEvent { - - private static final long serialVersionUID = 1L; - - private transient Path path; - - private final transient Map attrs; - - public DirectoryCreatedEvent(Object source, Path path, Map attrs) { - super(source); - this.path = path; - this.attrs = attrs; - } - - public Path getPath() { - return this.path; - } - - public Map getAttrs() { - return this.attrs; - } - - @Override - public String toString() { - return "DirectoryCreatedEvent [path=" + this.path - + ", attrs=" + this.attrs - + ", clientAddress=" + getSession().getClientAddress() + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/FileWrittenEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/FileWrittenEvent.java deleted file mode 100644 index 8c1e6630a40..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/FileWrittenEvent.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import java.nio.file.Path; - -import org.jspecify.annotations.Nullable; - -/** - * An event that is emitted when a file is written. - * - * @author Gary Russell - * @since 5.2 - * - */ -public class FileWrittenEvent extends ApacheMinaSftpEvent { - - private static final long serialVersionUID = 1L; - - private final String remoteHandle; - - private transient Path file; - - private final int dataLen; - - public FileWrittenEvent(Object source, String remoteHandle, Path file, int dataLen, @Nullable Throwable thrown) { - super(source, thrown); - this.remoteHandle = remoteHandle; - this.file = file; - this.dataLen = dataLen; - } - - public String getRemoteHandle() { - return this.remoteHandle; - } - - public Path getFile() { - return this.file; - } - - public int getDataLen() { - return this.dataLen; - } - - @Override - public String toString() { - return "FileWrittenEvent [remoteHandle=" + this.remoteHandle - + ", file=" + this.file - + ", dataLen=" + this.dataLen - + (this.cause == null ? "" : ", cause=" + this.cause) - + ", clientAddress=" + getSession().getClientAddress() + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/PathMovedEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/PathMovedEvent.java deleted file mode 100644 index c88dc049766..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/PathMovedEvent.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import java.nio.file.Path; - -import org.jspecify.annotations.Nullable; - -/** - * An event emitted when a path is moved. - * @author Gary Russell - * - * @since 5.2 - * - */ -public class PathMovedEvent extends ApacheMinaSftpEvent { - - private static final long serialVersionUID = 1L; - - private transient Path srcPath; - - private transient Path dstPath; - - public PathMovedEvent(Object source, Path srcPath, Path dstPath, @Nullable Throwable thrown) { - super(source, thrown); - this.srcPath = srcPath; - this.dstPath = dstPath; - } - - public Path getSrcPath() { - return this.srcPath; - } - - public Path getDstPath() { - return this.dstPath; - } - - @Override - public String toString() { - return "PathMovedEvent [srcPath=" + this.srcPath - + ", dstPath=" + this.dstPath - + (this.cause == null ? "" : ", cause=" + this.cause) - + ", clientAddress=" + getSession().getClientAddress() + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/PathRemovedEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/PathRemovedEvent.java deleted file mode 100644 index ed43df0e158..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/PathRemovedEvent.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import java.nio.file.Path; - -import org.jspecify.annotations.Nullable; - -/** - * An event emitted when a file or directory is removed. - * - * @author Gary Russell - * @since 5.2 - * - */ -public class PathRemovedEvent extends ApacheMinaSftpEvent { - - private static final long serialVersionUID = 1L; - - private transient Path path; - - private final boolean isDirectory; - - public PathRemovedEvent(Object source, Path path, boolean isDirectory, @Nullable Throwable thrown) { - super(source, thrown); - this.path = path; - this.isDirectory = isDirectory; - } - - public Path getPath() { - return this.path; - } - - public boolean isDirectory() { - return this.isDirectory; - } - - @Override - public String toString() { - return "PathRemovedEvent [path=" + this.path - + ", isDirectory=" + this.isDirectory - + (this.cause == null ? "" : ", cause=" + this.cause) - + ", clientAddress=" + getSession().getClientAddress() + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/SessionClosedEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/SessionClosedEvent.java deleted file mode 100644 index 12abfd7d716..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/SessionClosedEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import org.apache.sshd.server.session.ServerSession; - -/** - * An event emitted when a session is closed. - * - * @author Gary Russell - * @since 5.2 - * - */ -public class SessionClosedEvent extends ApacheMinaSftpEvent { - - private static final long serialVersionUID = 1L; - - public SessionClosedEvent(ServerSession session) { - super(session); - } - - @Override - public String toString() { - return "SessionClosedEvent [clientAddress=" + getSession().getClientAddress() + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/SessionOpenedEvent.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/SessionOpenedEvent.java deleted file mode 100644 index 880d73c3132..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/SessionOpenedEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.server; - -import org.apache.sshd.server.session.ServerSession; - -/** - * An event emitted when a session is opened. - * - * @author Gary Russell - * @since 5.2 - * - */ -public class SessionOpenedEvent extends ApacheMinaSftpEvent { - - private static final long serialVersionUID = 1L; - - private final int clientVersion; - - public SessionOpenedEvent(ServerSession session, int version) { - super(session); - this.clientVersion = version; - } - - public int getClientVersion() { - return this.clientVersion; - } - - @Override - public String toString() { - return "SessionOpenedEvent [clientVersion=" + this.clientVersion + ", clientAddress=" - + getSession().getClientAddress() + "]"; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/package-info.java deleted file mode 100644 index bb9434570b4..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/server/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes related to SFTP servers. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.server; diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java deleted file mode 100644 index 73ca9085920..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java +++ /dev/null @@ -1,524 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.session; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.time.Duration; -import java.util.Collection; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; - -import org.apache.sshd.client.SshClient; -import org.apache.sshd.client.auth.keyboard.UserInteraction; -import org.apache.sshd.client.auth.password.PasswordIdentityProvider; -import org.apache.sshd.client.channel.ChannelSubsystem; -import org.apache.sshd.client.config.hosts.HostConfigEntry; -import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier; -import org.apache.sshd.client.keyverifier.RejectAllServerKeyVerifier; -import org.apache.sshd.client.keyverifier.ServerKeyVerifier; -import org.apache.sshd.client.session.ClientSession; -import org.apache.sshd.common.PropertyResolverUtils; -import org.apache.sshd.common.SshConstants; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.keyprovider.KeyIdentityProvider; -import org.apache.sshd.common.util.buffer.Buffer; -import org.apache.sshd.common.util.io.resource.AbstractIoResource; -import org.apache.sshd.common.util.io.resource.IoResource; -import org.apache.sshd.common.util.net.SshdSocketAddress; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.sftp.client.SftpClient; -import org.apache.sshd.sftp.client.SftpErrorDataHandler; -import org.apache.sshd.sftp.client.SftpMessage; -import org.apache.sshd.sftp.client.SftpVersionSelector; -import org.apache.sshd.sftp.client.impl.AbstractSftpClient; -import org.apache.sshd.sftp.client.impl.DefaultSftpClient; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.core.io.Resource; -import org.springframework.integration.context.IntegrationContextUtils; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.file.remote.session.SharedSessionCapable; -import org.springframework.util.Assert; - -/** - * Factory for creating {@link SftpSession} instances. - *

- * The {@link #createSftpClient(ClientSession, SftpVersionSelector, SftpErrorDataHandler)} - * can be overridden to provide a custom {@link SftpClient}. - * The {@link ConcurrentSftpClient} is used by default. - * - * @author Josh Long - * @author Mario Gray - * @author Oleg Zhurakousky - * @author Gunnar Hillert - * @author Gary Russell - * @author David Liu - * @author Pat Turner - * @author Artem Bilan - * @author Krzysztof Debski - * @author Auke Zaaiman - * @author Christian Tzolov - * @author Adama Sorho - * @author Darryl Smith - * @author Alastair Mailer - * - * @since 2.0 - */ -public class DefaultSftpSessionFactory - implements SessionFactory, SharedSessionCapable, DisposableBean { - - private final Lock lock = new ReentrantLock(); - - private final SshClient sshClient; - - private volatile boolean initialized; - - private final boolean isSharedSession; - - private final @Nullable Lock sharedSessionLock; - - private boolean isInnerClient = false; - - @SuppressWarnings("NullAway.Init") - private String host; - - private int port = SshConstants.DEFAULT_PORT; - - @SuppressWarnings("NullAway.Init") - private String user; - - private @Nullable String password; - - private @Nullable HostConfigEntry hostConfig; - - private @Nullable Resource knownHosts; - - private @Nullable Resource privateKey; - - private @Nullable String privateKeyPassphrase; - - private @Nullable UserInteraction userInteraction; - - private boolean allowUnknownKeys = false; - - private Integer timeout = (int) IntegrationContextUtils.DEFAULT_TIMEOUT; - - private SftpVersionSelector sftpVersionSelector = SftpVersionSelector.CURRENT; - - private volatile @Nullable SftpClient sharedSftpClient; - - private Consumer sshClientConfigurer = (sshClient) -> { - }; - - public DefaultSftpSessionFactory() { - this(false); - } - - /** - * @param isSharedSession true if the session is to be shared. - */ - public DefaultSftpSessionFactory(boolean isSharedSession) { - this(SshClient.setUpDefaultClient(), isSharedSession); - this.isInnerClient = true; - } - - /** - * Instantiate based on the provided {@link SshClient}, e.g. some extension for HTTP/SOCKS. - * @param sshClient the {@link SshClient} instance. - * @param isSharedSession true if the session is to be shared. - */ - public DefaultSftpSessionFactory(SshClient sshClient, boolean isSharedSession) { - Assert.notNull(sshClient, "'sshClient' must not be null"); - this.sshClient = sshClient; - this.isSharedSession = isSharedSession; - if (this.isSharedSession) { - this.sharedSessionLock = new ReentrantLock(); - } - else { - this.sharedSessionLock = null; - } - } - - /** - * The url of the host you want to connect to. This is a mandatory property. - * @param host The host. - * @see SshClient#connect(String, String, int) - */ - public void setHost(String host) { - this.host = host; - } - - /** - * The port over which the SFTP connection shall be established. If not specified, - * this value defaults to 22. If specified, this properties must - * be a positive number. - * @param port The port. - * @see SshClient#connect(String, String, int) - */ - public void setPort(int port) { - this.port = port; - } - - /** - * The remote user to use. This is a mandatory property. - * @param user The user. - * @see SshClient#connect(String, String, int) - */ - public void setUser(String user) { - this.user = user; - } - - /** - * The password to authenticate against the remote host. If a password is - * not provided, then a {@link DefaultSftpSessionFactory#setPrivateKey(Resource) privateKey} is - * mandatory. - * @param password The password. - * @see SshClient#setPasswordIdentityProvider(PasswordIdentityProvider) - */ - public void setPassword(String password) { - Assert.state(this.isInnerClient, - "A password must be configured on the externally provided SshClient instance"); - this.password = password; - } - - /** - * Provide a {@link HostConfigEntry} as an alternative for the user/host/port options. - * Can be configured with a proxy jump property. - * @param hostConfig the {@link HostConfigEntry} for connection. - * @since 6.0 - * @see SshClient#connect(HostConfigEntry) - */ - public void setHostConfig(HostConfigEntry hostConfig) { - this.hostConfig = hostConfig; - } - - /** - * Specifies a {@link Resource} that will be used for a host key repository. - * The data has to have the same format as OpenSSH's known_hosts file. - * @param knownHosts the resource for known hosts. - * @since 5.2.5 - * @see SshClient#setServerKeyVerifier(ServerKeyVerifier) - */ - public void setKnownHostsResource(Resource knownHosts) { - Assert.state(this.isInnerClient, - "Known hosts must be configured on the externally provided SshClient instance"); - this.knownHosts = knownHosts; - } - - /** - * Allows you to set a {@link Resource}, which represents the location of the - * private key used for authenticating against the remote host. If the privateKey - * is not provided, then the {@link DefaultSftpSessionFactory#setPassword(String) password} - * property is mandatory. - * @param privateKey The private key. - * @see SshClient#setKeyIdentityProvider(KeyIdentityProvider) - */ - public void setPrivateKey(Resource privateKey) { - Assert.state(this.isInnerClient, - "A private key auth must be configured on the externally provided SshClient instance"); - this.privateKey = privateKey; - } - - /** - * The password for the private key. Optional. - * @param privateKeyPassphrase The private key passphrase. - * @see SshClient#setKeyIdentityProvider(KeyIdentityProvider) - */ - public void setPrivateKeyPassphrase(String privateKeyPassphrase) { - Assert.state(this.isInnerClient, - "A private key auth must be configured on the externally provided SshClient instance"); - this.privateKeyPassphrase = privateKeyPassphrase; - } - - /** - * Provide a {@link UserInteraction} which exposes control over dealing with new keys or key - * changes. As Spring Integration will not normally allow user interaction, the - * implementation must respond to SSH protocol calls in a suitable way. - * @param userInteraction the UserInteraction. - * @since 4.1.7 - * @see SshClient#setUserInteraction(UserInteraction) - */ - public void setUserInteraction(UserInteraction userInteraction) { - Assert.state(this.isInnerClient, - "A `UserInteraction` must be configured on the externally provided SshClient instance"); - this.userInteraction = userInteraction; - } - - /** - * When no {@link #knownHosts} has been provided, set to true to unconditionally allow - * connecting to an unknown host or when a host's key has changed (see - * {@link #setKnownHostsResource(Resource) knownHosts}). Default false (since 4.2). - * Set to true if a knownHosts file is not provided. - * @param allowUnknownKeys true to allow connecting to unknown hosts. - * @since 4.1.7 - */ - public void setAllowUnknownKeys(boolean allowUnknownKeys) { - Assert.state(this.isInnerClient, - "An `AcceptAllServerKeyVerifier` must be configured on the externally provided SshClient instance"); - this.allowUnknownKeys = allowUnknownKeys; - } - - /** - * The timeout property is used as the socket timeout parameter, as well as - * the default connection timeout. Defaults to {@code 30 seconds}. - * Setting to {@code 0} means no timeout; to {@code null} - infinite wait. - * @param timeout the timeout. - * @see org.apache.sshd.client.future.ConnectFuture#verify(Duration, org.apache.sshd.common.future.CancelOption...) - */ - public void setTimeout(Integer timeout) { - this.timeout = timeout; - } - - public void setSftpVersionSelector(SftpVersionSelector sftpVersionSelector) { - Assert.notNull(sftpVersionSelector, "'sftpVersionSelector' must noy be null"); - this.sftpVersionSelector = sftpVersionSelector; - } - - /** - * Set a {@link Consumer} as a callback to further customize an internal {@link SshClient} instance. - * For example, to set custom values for its properties using {@link PropertyResolverUtils#updateProperty} API. - * @param sshClientConfigurer the {@link Consumer} to configure an internal {@link SshClient} instance. - * @since 6.4 - * @see SshClient - * @see PropertyResolverUtils#updateProperty - */ - public void setSshClientConfigurer(Consumer sshClientConfigurer) { - Assert.state(this.isInnerClient, "Cannot mutate externally provided SshClient"); - Assert.notNull(sshClientConfigurer, "'sshClientConfigurer' must noy be null"); - this.sshClientConfigurer = sshClientConfigurer; - } - - @Override - public SftpSession getSession() { - SftpSession sftpSession; - if (this.sharedSessionLock != null) { - this.sharedSessionLock.lock(); - } - SftpClient sftpClient = this.sharedSftpClient; - try { - boolean freshSftpClient = false; - if (sftpClient == null || !sftpClient.isOpen()) { - sftpClient = createSftpClient(initClientSession(), this.sftpVersionSelector, SftpErrorDataHandler.EMPTY); - freshSftpClient = true; - } - sftpSession = new SftpSession(sftpClient, this.isSharedSession); - sftpSession.connect(); - if (this.isSharedSession && freshSftpClient) { - this.sharedSftpClient = sftpClient; - } - } - catch (Exception ex) { - throw new IllegalStateException("failed to create SFTP Session", ex); - } - finally { - if (this.sharedSessionLock != null) { - this.sharedSessionLock.unlock(); - } - } - return sftpSession; - } - - private ClientSession initClientSession() throws IOException { - Assert.hasText(this.host, "host must not be empty"); - Assert.hasText(this.user, "user must not be empty"); - - initClient(); - - Duration verifyTimeout = this.timeout != null ? Duration.ofMillis(this.timeout) : null; - HostConfigEntry config = this.hostConfig; - if (config == null) { - config = new HostConfigEntry(SshdSocketAddress.isIPv6Address(this.host) ? "" : this.host, this.host, - this.port, this.user); - } - ClientSession clientSession = - this.sshClient.connect(config) - .verify(verifyTimeout) - .getSession(); - - try { - clientSession.auth().verify(verifyTimeout); - } - catch (IOException ex) { - clientSession.close(); - throw ex; - } - - return clientSession; - } - - private void initClient() throws IOException { - if (!this.initialized) { - this.lock.lock(); - try { - if (!this.initialized) { - doInitClient(); - this.initialized = true; - } - } - finally { - this.lock.unlock(); - } - } - } - - private void doInitClient() throws IOException { - if (this.port <= 0) { - this.port = SshConstants.DEFAULT_PORT; - } - - doInitInnerClient(); - - this.sshClient.start(); - } - - private void doInitInnerClient() throws IOException { - if (this.isInnerClient) { - ServerKeyVerifier serverKeyVerifier = - this.allowUnknownKeys ? AcceptAllServerKeyVerifier.INSTANCE : RejectAllServerKeyVerifier.INSTANCE; - if (this.knownHosts != null) { - serverKeyVerifier = new ResourceKnownHostsServerKeyVerifier(this.knownHosts); - } - this.sshClient.setServerKeyVerifier(serverKeyVerifier); - - this.sshClient.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(this.password)); - if (this.privateKey != null) { - IoResource privateKeyResource = - new AbstractIoResource<>(Resource.class, this.privateKey) { - - @Override - public InputStream openInputStream() throws IOException { - return getResourceValue().getInputStream(); - } - - }; - try { - Collection keys = - SecurityUtils.getKeyPairResourceParser() - .loadKeyPairs(null, privateKeyResource, - FilePasswordProvider.of(this.privateKeyPassphrase)); - this.sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys)); - } - catch (GeneralSecurityException ex) { - throw new IOException("Cannot load private key: " + this.privateKey.getFilename(), ex); - } - } - this.sshClient.setUserInteraction(this.userInteraction); - this.sshClientConfigurer.accept(this.sshClient); - } - } - - @Override - public final boolean isSharedSession() { - return this.isSharedSession; - } - - @Override - public void resetSharedSession() { - Assert.state(this.isSharedSession, "Shared sessions are not being used"); - this.sharedSftpClient = null; - } - - /** - * Can be overridden to provide a custom {@link SftpClient} to {@link #getSession()}. - * @param clientSession the {@link ClientSession} - * @param initialVersionSelector the initial {@link SftpVersionSelector} - * @param errorDataHandler the {@link SftpErrorDataHandler} to handle incoming data - * through the error stream. - * @return {@link SftpClient} - * @throws IOException if failed to initialize - * @since 6.1.3 - */ - protected SftpClient createSftpClient( - ClientSession clientSession, SftpVersionSelector initialVersionSelector, - SftpErrorDataHandler errorDataHandler) throws IOException { - - return new ConcurrentSftpClient(clientSession, initialVersionSelector, errorDataHandler); - } - - @Override - public void destroy() { - if (this.isInnerClient && this.sshClient != null && this.sshClient.isStarted()) { - this.sshClient.stop(); - } - - SftpClient sharedSftpClientToClose = this.sharedSftpClient; - if (sharedSftpClientToClose != null) { - try { - sharedSftpClientToClose.close(); - } - catch (IOException ex) { - throw new UncheckedIOException("failed to close an SFTP client", ex); - } - - try { - ClientSession session = sharedSftpClientToClose.getSession(); - if (session != null && session.isOpen()) { - session.close(); - } - } - catch (IOException ex) { - throw new UncheckedIOException("failed to close an SFTP client (session)", ex); - } - } - } - - /** - * The {@link DefaultSftpClient} extension to lock the {@link #send(int, Buffer)} - * for concurrent interaction. - *

- * Also sets the provided {@link #timeout} as a {@link AbstractSftpClient#SFTP_CLIENT_CMD_TIMEOUT} property. - */ - protected class ConcurrentSftpClient extends DefaultSftpClient { - - private final Lock sftpWriteLock = new ReentrantLock(); - - protected ConcurrentSftpClient(ClientSession clientSession, SftpVersionSelector initialVersionSelector, - SftpErrorDataHandler errorDataHandler) throws IOException { - - super(clientSession, initialVersionSelector, errorDataHandler); - } - - @Override - public SftpMessage write(int cmd, Buffer buffer) throws IOException { - this.sftpWriteLock.lock(); - try { - SftpMessage sftpMessage = super.write(cmd, buffer); - sftpMessage.waitUntilSent(); - return sftpMessage; - } - finally { - this.sftpWriteLock.unlock(); - } - } - - @Override - protected ChannelSubsystem createSftpChannelSubsystem(ClientSession clientSession) { - ChannelSubsystem sftpChannelSubsystem = super.createSftpChannelSubsystem(clientSession); - PropertyResolverUtils.updateProperty(sftpChannelSubsystem, - AbstractSftpClient.SFTP_CLIENT_CMD_TIMEOUT.getName(), DefaultSftpSessionFactory.this.timeout); - return sftpChannelSubsystem; - } - - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/ResourceKnownHostsServerKeyVerifier.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/ResourceKnownHostsServerKeyVerifier.java deleted file mode 100644 index 7989764d138..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/ResourceKnownHostsServerKeyVerifier.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2022-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.session; - -import java.io.IOException; -import java.net.SocketAddress; -import java.security.GeneralSecurityException; -import java.security.PublicKey; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.TreeSet; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.sshd.client.config.hosts.KnownHostEntry; -import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier; -import org.apache.sshd.client.keyverifier.ServerKeyVerifier; -import org.apache.sshd.client.session.ClientSession; -import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; -import org.apache.sshd.common.config.keys.KeyUtils; -import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.net.SshdSocketAddress; - -import org.springframework.core.io.Resource; -import org.springframework.util.Assert; - -/** - * A {@link ServerKeyVerifier} implementation for a {@link Resource} abstraction. - * The logic is similar to the {@link KnownHostsServerKeyVerifier}, but in read-only mode. - * - * @author Artem Bilan - * - * @since 6.0 - * - * @see KnownHostsServerKeyVerifier - */ -public class ResourceKnownHostsServerKeyVerifier implements ServerKeyVerifier { - - private static final Log logger = LogFactory.getLog(ResourceKnownHostsServerKeyVerifier.class); - - private final Supplier> keysSupplier; - - public ResourceKnownHostsServerKeyVerifier(Resource knownHostsResource) { - Assert.notNull(knownHostsResource, "'knownHostsResource' must not be null"); - this.keysSupplier = GenericUtils.memoizeLock(getKnownHostSupplier(knownHostsResource)); - } - - @Override - public boolean verifyServerKey(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey) { - Collection knownHosts = this.keysSupplier.get(); - List matches = - findKnownHostEntries(clientSession, remoteAddress, knownHosts); - - if (matches.isEmpty()) { - return false; - } - - String serverKeyType = KeyUtils.getKeyType(serverKey); - - return matches.stream() - .filter(match -> serverKeyType.equals(match.getHostEntry().getKeyEntry().getKeyType())) - .filter(match -> KeyUtils.compareKeys(match.getServerKey(), serverKey)) - .anyMatch(match -> !"revoked".equals(match.getHostEntry().getMarker())); - } - - private static Supplier> getKnownHostSupplier( - Resource knownHostsResource) { - - return () -> { - try { - Collection entries = - KnownHostEntry.readKnownHostEntries(knownHostsResource.getInputStream(), true); - List keys = new ArrayList<>(entries.size()); - for (KnownHostEntry entry : entries) { - keys.add(new KnownHostsServerKeyVerifier.HostEntryPair(entry, resolveHostKey(entry))); - } - return keys; - } - catch (Exception ex) { - logger.warn("Known hosts cannot be loaded from the: " + knownHostsResource, ex); - return Collections.emptyList(); - } - }; - } - - private static PublicKey resolveHostKey(KnownHostEntry entry) throws IOException, GeneralSecurityException { - AuthorizedKeyEntry authEntry = entry.getKeyEntry(); - Assert.notNull(authEntry, () -> "No key extracted from " + entry); - return authEntry.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING); - } - - private static List findKnownHostEntries( - ClientSession clientSession, SocketAddress remoteAddress, - Collection knownHosts) { - - if (GenericUtils.isEmpty(knownHosts)) { - return Collections.emptyList(); - } - - Collection candidates = resolveHostNetworkIdentities(clientSession, remoteAddress); - - if (GenericUtils.isEmpty(candidates)) { - return Collections.emptyList(); - } - - List matches = new ArrayList<>(); - for (KnownHostsServerKeyVerifier.HostEntryPair match : knownHosts) { - KnownHostEntry entry = match.getHostEntry(); - for (SshdSocketAddress host : candidates) { - if (entry.isHostMatch(host.getHostName(), host.getPort())) { - matches.add(match); - break; - } - } - } - - return matches; - } - - private static Collection resolveHostNetworkIdentities( - ClientSession clientSession, SocketAddress remoteAddress) { - /* - * NOTE !!! we do not resolve the fully-qualified name to avoid long DNS timeouts. - * Instead, we use the reported peer address and the original connection target host - */ - Collection candidates = new TreeSet<>(SshdSocketAddress.BY_HOST_AND_PORT); - candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress)); - SocketAddress connectAddress = clientSession.getConnectAddress(); - candidates.add(SshdSocketAddress.toSshdSocketAddress(connectAddress)); - return candidates; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpFileInfo.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpFileInfo.java deleted file mode 100644 index 46ece159229..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpFileInfo.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.session; - -import java.nio.file.attribute.PosixFilePermissions; - -import org.apache.sshd.sftp.client.SftpClient; -import org.apache.sshd.sftp.common.SftpHelper; - -import org.springframework.integration.file.remote.AbstractFileInfo; -import org.springframework.util.Assert; - -/** - * A {@link org.springframework.integration.file.remote.FileInfo} implementation for SFTP. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 2.1 - */ -public class SftpFileInfo extends AbstractFileInfo { - - private final SftpClient.DirEntry lsEntry; - - private final SftpClient.Attributes attrs; - - public SftpFileInfo(SftpClient.DirEntry lsEntry) { - Assert.notNull(lsEntry, "'lsEntry' must not be null"); - this.lsEntry = lsEntry; - this.attrs = lsEntry.getAttributes(); - } - - /** - * @see SftpClient.Attributes#isDirectory() - */ - @Override - public boolean isDirectory() { - return this.attrs.isDirectory(); - } - - /** - * @see SftpClient.Attributes#isSymbolicLink() - */ - @Override - public boolean isLink() { - return this.attrs.isSymbolicLink(); - } - - /** - * @see SftpClient.Attributes#getSize() - */ - @Override - public long getSize() { - return this.attrs.getSize(); - } - - /** - * @see SftpClient.Attributes#getModifyTime() - */ - @Override - public long getModified() { - return this.attrs.getModifyTime().toMillis(); - } - - /** - * @see SftpClient.DirEntry#getFilename() - */ - @Override - public String getFilename() { - return this.lsEntry.getFilename(); - } - - @Override - public String getPermissions() { - return PosixFilePermissions.toString(SftpHelper.permissionsToAttributes(this.attrs.getPermissions())); - } - - @Override - public SftpClient.DirEntry getFileInfo() { - return this.lsEntry; - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplate.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplate.java deleted file mode 100644 index 28602bbd6df..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplate.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2014-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.session; - -import java.util.List; - -import org.apache.sshd.sftp.client.SftpClient; -import org.apache.sshd.sftp.common.SftpConstants; -import org.apache.sshd.sftp.common.SftpException; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.ClientCallback; -import org.springframework.integration.file.remote.RemoteFileTemplate; -import org.springframework.integration.file.remote.session.SessionFactory; - -/** - * SFTP version of {@code RemoteFileTemplate} providing type-safe access to - * the underlying ChannelSftp object. - * - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.1 - * - */ -public class SftpRemoteFileTemplate extends RemoteFileTemplate { - - protected static final List NOT_DIRTY_STATUSES = // NOSONAR - List.of( - SftpConstants.SSH_FX_NO_SUCH_FILE, - SftpConstants.SSH_FX_NO_SUCH_PATH, - SftpConstants.SSH_FX_INVALID_FILENAME, - SftpConstants.SSH_FX_INVALID_HANDLE, - SftpConstants.SSH_FX_FILE_ALREADY_EXISTS, - SftpConstants.SSH_FX_DIR_NOT_EMPTY, - SftpConstants.SSH_FX_NOT_A_DIRECTORY, - SftpConstants.SSH_FX_EOF, - SftpConstants.SSH_FX_CANNOT_DELETE, - SftpConstants.SSH_FX_FILE_IS_A_DIRECTORY, - SftpConstants.SSH_FX_FILE_CORRUPT - ); - - public SftpRemoteFileTemplate(SessionFactory sessionFactory) { - super(sessionFactory); - } - - @SuppressWarnings("unchecked") - @Override - public @Nullable T executeWithClient(final ClientCallback callback) { - return doExecuteWithClient((ClientCallback) callback); - } - - protected @Nullable T doExecuteWithClient(final ClientCallback callback) { - return execute(session -> callback.doWithClient((SftpClient) session.getClientInstance())); - } - - @Override - protected boolean shouldMarkSessionAsDirty(Exception ex) { - SftpException sftpException = findSftpException(ex); - if (sftpException != null) { - return isStatusDirty(sftpException.getStatus()); - } - else { - return super.shouldMarkSessionAsDirty(ex); - } - } - - /** - * Check if {@link SftpException#getStatus()} is treated as fatal. - * @param status the value from {@link SftpException#getStatus()}. - * @return true if {@link SftpException#getStatus()} is treated as fatal. - * @since 6.0.8 - */ - protected boolean isStatusDirty(int status) { - return !NOT_DIRTY_STATUSES.contains(status); - } - - private static @Nullable SftpException findSftpException(@Nullable Throwable ex) { - if (ex == null) { - return null; - } - - if (ex instanceof SftpException sftpException) { - return sftpException; - } - - return findSftpException(ex.getCause()); - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java deleted file mode 100644 index a2934a51b5a..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.session; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.net.SocketAddress; -import java.time.Duration; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.apache.sshd.client.session.ClientSession; -import org.apache.sshd.common.util.net.SshdSocketAddress; -import org.apache.sshd.sftp.SftpModuleProperties; -import org.apache.sshd.sftp.client.SftpClient; -import org.apache.sshd.sftp.common.SftpConstants; -import org.apache.sshd.sftp.common.SftpException; -import org.jspecify.annotations.Nullable; - -import org.springframework.integration.file.remote.session.Session; -import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.PatternMatchUtils; -import org.springframework.util.StringUtils; - -/** - * Default SFTP {@link Session} implementation. Wraps a MINA SSHD session instance. - * - * @author Josh Long - * @author Mario Gray - * @author Mark Fisher - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Artem Bilan - * @author Christian Tzolov - * @author Darryl Smith - * @since 2.0 - */ -public class SftpSession implements Session { - - private final SftpClient sftpClient; - - private final boolean isSharedClient; - - public SftpSession(SftpClient sftpClient) { - this(sftpClient, false); - } - - /** - * Construct an instance based on a {@link SftpClient} and its {@code shared} status. - * When {@code isSharedClient == true}, the {@link #close()} is void. - * @param sftpClient the {@link SftpClient} to use. - * @param isSharedClient whether the {@link SftpClient} is shared. - * @since 6.3.9 - */ - public SftpSession(SftpClient sftpClient, boolean isSharedClient) { - Assert.notNull(sftpClient, "'sftpClient' must not be null"); - this.sftpClient = sftpClient; - this.isSharedClient = isSharedClient; - } - - @Override - public boolean remove(String path) throws IOException { - try { - this.sftpClient.remove(path); - } - catch (SftpException sftpEx) { - if (SftpConstants.SSH_FX_NO_SUCH_FILE == sftpEx.getStatus()) { - return false; - } - else { - throw sftpEx; - } - } - - return true; - } - - @Override - public SftpClient.DirEntry[] list(@Nullable String path) throws IOException { - return doList(path) - .toArray(SftpClient.DirEntry[]::new); - } - - @Override - public String[] listNames(@Nullable String path) throws IOException { - return doList(path) - .map(SftpClient.DirEntry::getFilename) - .toArray(String[]::new); - } - - public Stream doList(@Nullable String path) throws IOException { - String validPath = path != null ? path : "."; - String remotePath = StringUtils.trimTrailingCharacter(validPath, '/'); - String remoteDir = remotePath; - int lastIndex = remotePath.lastIndexOf('/'); - if (lastIndex > 0) { - remoteDir = remoteDir.substring(0, lastIndex); - } - String remoteFile = lastIndex > 0 ? remotePath.substring(lastIndex + 1) : null; - boolean isPattern = remoteFile != null && remoteFile.contains("*"); - - if (!isPattern && remoteFile != null) { - SftpClient.Attributes attributes = this.sftpClient.stat(validPath); - if (!attributes.isDirectory()) { - return Stream.of(new SftpClient.DirEntry(remoteFile, validPath, attributes)); - } - else { - remoteDir = remotePath; - } - } - remoteDir = normalizePath(remoteDir); - return StreamSupport.stream(this.sftpClient.readDir(remoteDir).spliterator(), false) - .filter((entry) -> !isPattern || PatternMatchUtils.simpleMatch(remoteFile, entry.getFilename())); - } - - @Override - public void read(String source, OutputStream os) throws IOException { - InputStream is = readRaw(source); - FileCopyUtils.copy(is, os); - } - - @Override - public InputStream readRaw(String source) throws IOException { - return this.sftpClient.read(normalizePath(source)); - } - - private String normalizePath(String path) throws IOException { - return !path.isEmpty() && path.charAt(0) == '/' ? path : this.sftpClient.canonicalPath(path); - } - - @Override - public boolean finalizeRaw() { - return true; - } - - @Override - public void write(InputStream inputStream, String destination) throws IOException { - OutputStream outputStream = this.sftpClient.write(destination); - FileCopyUtils.copy(inputStream, outputStream); - } - - @Override - public void append(InputStream inputStream, String destination) throws IOException { - OutputStream outputStream = - this.sftpClient.write(destination, - SftpClient.OpenMode.Create, - SftpClient.OpenMode.Write, - SftpClient.OpenMode.Append); - FileCopyUtils.copy(inputStream, outputStream); - } - - @Override - public void close() { - if (this.isSharedClient) { - return; - } - - try { - this.sftpClient.close(); - } - catch (IOException ex) { - throw new UncheckedIOException("failed to close an SFTP client", ex); - } - - try { - ClientSession session = this.sftpClient.getSession(); - if (session != null && session.isOpen()) { - session.close(); - } - } - catch (IOException ex) { - throw new UncheckedIOException("failed to close an SFTP client (session)", ex); - } - } - - @Override - public boolean isOpen() { - return this.sftpClient.isOpen(); - } - - @Override - public void rename(String pathFrom, String pathTo) throws IOException { - if (this.sftpClient.getVersion() >= SftpConstants.SFTP_V5) { - this.sftpClient.rename(pathFrom, pathTo, SftpClient.CopyMode.Overwrite); - } - else { - remove(pathTo); - this.sftpClient.rename(pathFrom, pathTo); - } - } - - @Override - public boolean mkdir(String remoteDirectory) throws IOException { - this.sftpClient.mkdir(remoteDirectory); - return true; - } - - @Override - public boolean rmdir(String remoteDirectory) throws IOException { - this.sftpClient.rmdir(remoteDirectory); - return true; - } - - @Override - public boolean exists(String path) { - try { - this.sftpClient.stat(normalizePath(path)); - return true; - } - catch (SftpException ex) { - if (SftpConstants.SSH_FX_NO_SUCH_FILE == ex.getStatus()) { - return false; - } - else { - throw new UncheckedIOException("Cannot check 'lstat' for path " + path, ex); - } - } - catch (IOException ex) { - throw new UncheckedIOException("Cannot check 'lstat' for path " + path, ex); - } - } - - void connect() { - try { - if (!this.sftpClient.isOpen()) { - Duration initializationTimeout = - SftpModuleProperties.SFTP_CHANNEL_OPEN_TIMEOUT.getRequired(this.sftpClient.getSession()); - this.sftpClient.getClientChannel().open().verify(initializationTimeout); - } - } - catch (IOException ex) { - close(); - throw new UncheckedIOException("failed to connect an SFTP client", ex); - } - } - - @Override - public SftpClient getClientInstance() { - return this.sftpClient; - } - - @Override - public String getHostPort() { - SocketAddress connectAddress = this.sftpClient.getSession().getConnectAddress(); - return SshdSocketAddress.toAddressString(connectAddress) + ':' + SshdSocketAddress.toAddressPort(connectAddress); - } - - @Override - public boolean test() { - return isOpen() && doTest(); - } - - private boolean doTest() { - try { - this.sftpClient.canonicalPath(""); - return true; - } - catch (@SuppressWarnings("unused") Exception ex) { - return false; - } - } - -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/package-info.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/package-info.java deleted file mode 100644 index 5c653d6b5f5..00000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Provides classes supporting SFTP sessions. - */ -@org.jspecify.annotations.NullMarked -package org.springframework.integration.sftp.session; diff --git a/spring-integration-sftp/src/main/resources/META-INF/spring.handlers b/spring-integration-sftp/src/main/resources/META-INF/spring.handlers deleted file mode 100644 index 8e52f8fe484..00000000000 --- a/spring-integration-sftp/src/main/resources/META-INF/spring.handlers +++ /dev/null @@ -1 +0,0 @@ -http\://www.springframework.org/schema/integration/sftp=org.springframework.integration.sftp.config.SftpNamespaceHandler diff --git a/spring-integration-sftp/src/main/resources/META-INF/spring.schemas b/spring-integration-sftp/src/main/resources/META-INF/spring.schemas deleted file mode 100644 index a3f2b97fba6..00000000000 --- a/spring-integration-sftp/src/main/resources/META-INF/spring.schemas +++ /dev/null @@ -1,25 +0,0 @@ -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.1.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.2.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-3.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.1.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.2.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.3.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-5.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-5.1.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-5.2.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -http\://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.1.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.2.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-3.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.1.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.2.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-4.3.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-5.0.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-5.1.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp-5.2.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd -https\://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd=org/springframework/integration/sftp/config/spring-integration-sftp.xsd - diff --git a/spring-integration-sftp/src/main/resources/META-INF/spring.tooling b/spring-integration-sftp/src/main/resources/META-INF/spring.tooling deleted file mode 100644 index 1d4785006a4..00000000000 --- a/spring-integration-sftp/src/main/resources/META-INF/spring.tooling +++ /dev/null @@ -1,4 +0,0 @@ -# Tooling related information for the integration sftp namespace -http\://www.springframework.org/schema/integration/sftp@name=integration sftp Namespace -http\://www.springframework.org/schema/integration/sftp@prefix=int-sftp -http\://www.springframework.org/schema/integration/sftp@icon=org/springframework/integration/sftp/config/spring-integration-sftp.gif diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp.gif b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp.gif deleted file mode 100644 index 66a798e9a1e..00000000000 Binary files a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp.gif and /dev/null differ diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp.xsd deleted file mode 100644 index 4a591abe92e..00000000000 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp.xsd +++ /dev/null @@ -1,690 +0,0 @@ - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.file.remote.handler.FileTransferringMessageHandler' - for the 'SftpRemoteFileTemplate' that writes files to a remote SFTP endpoint. - - - - - - - - - - - - - - - - - - Identifies channel attached to this adapter. - This channel could be the receiving channel. - - - - - - - - Specifies the order for invocation when this - endpoint is connected as a - subscriber to a channel. This is - particularly relevant when that channel - is using a "failover" - dispatching strategy, or when a failure in - the delivery to one - subscriber should signal that - the message should not be sent to - subscribers with a higher 'order' - attribute. It has no effect - when this - endpoint itself is a Polling Consumer for a channel - with a queue. - - - - - - - - - - - - - - Configures a 'SourcePollingChannelAdapter' Endpoint for the - 'org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource' - that synchronizes with a remote SFTP endpoint. - - - - - - - - - Allows you to provide a SpEL expression to - generate the file name of - the local (transferred) file. The root - object of the SpEL - evaluation is the name of the original - file. - For example, a valid expression would be "#this.toUpperCase() + - '.a'" where #this represents the - original name of the remote - file. - - - - - - - - - - - - - - - - - Allows you to specify a reference to a - [org.springframework.integration.file.filters.FileListFilter] - bean. This filter is applied to files after they have been - retrieved. The default is an AcceptOnceFileListFilter which means that, - even if a new instance of a file is retrieved from the remote server, - a message won't be generated. The filter provided here is combined - with a filter that prevents the message source from processing - files that are currently being downloaded. - - - - - - - Identifies the directory path (e.g., - "/local/mytransfers") where files - will be transferred TO. - - - - - - - Tells this adapter if the local directory must - be auto-created if it - doesn't exist. Default is TRUE. - - - - - - - Specify whether to delete the remote source - file after copying. - By default, the remote files will NOT be - deleted. - - - - - - - Specify whether to preserve the modified timestamp from the remote source - file on the local file after copying. - By default, the remote timestamp will NOT be - preserved. - - - - - - - - - - - - Reference to a custom DirectoryScanner implementation. - - - - - - - - - - - - Reference to a MetadataStore for saving remote files information between - synchronization and polling. - - - - - - - Specify a prefix for metadata store to distinguish keys from another places - where the same shared store is used. - By default, the remote a component name is used. - - - - - - - - - - - - - Configures a 'SourcePollingChannelAdapter' Endpoint for the - 'org.springframework.integration.ftp.inbound.FtpInboundStreamingMessageSource'. - - - - - - - - - - - - - - - - - - - Configures a Consumer Endpoint for the - 'org.springframework.integration.sftp.gateway.SftpOutboundGateway used to issue SFTP commands. - - - - - - - - - - - - - - sftp command. - - - - - - - - - - - - - - - The 'MessageSessionCallback' bean reference to perform custom operation(s) on 'Session' - with 'requestMessage'. - - - - - - - sftp command options; for ls, -1 means just - return the file names - (otherwise file - metadata is returned, -dirs - means include directories (not included by - default), - -links means - include links (not included by default); for get, -P means - preserve - timestamp from remote file. - - - - - - - SpEL expression representing the path in the - command (e.g. ls path to - list the files in directory path). - - - - - - - SpEL expression representing the path for the - new filename when using the 'mv' command. Defaults - to "headers.['file_renameTo']". - - - - - - - - - - - - Identifies the request channel attached to this - gateway. - - - - - - - - - - - - Identifies the reply channel attached to this - gateway. - - - - - - - - - - - - - - - - - Allows you to specify a reference to - [org.springframework.integration.file.filters.FileListFilter] - bean. This filter acts against the remote server view when using the 'ls' - or 'mget' commands. - Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed. - - - - - - - - - - - - Allows you to provide file name pattern to - determine the file names retrieved by the 'ls' and 'mget' commands - and is based - on simple pattern matching algorithm (e.g., "*.txt, fo*.txt" - etc.) - Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed. - - - - - - - Allows you to provide Regular Expression to - determine the file names retrieved by the 'ls' and 'mget' commands. - (e.g., - "f[o]+\.txt" etc.) - Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed. - - - - - - - - - - - - Allows you to specify a reference to - [org.springframework.integration.file.filters.FileListFilter] - bean. This filter acts on the local file system when using the 'mput' command. - Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed. - - - - - - - - - - - - Allows you to provide file name pattern to - determine the file names sent by the 'mput' command - and is based - on simple pattern matching algorithm (e.g., "*.txt, fo*.txt" - etc.) - Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed. - - - - - - - Allows you to provide Regular Expression to - determine the file names sent by the 'mput' command - (e.g., - "f[o]+\.txt" etc.) - Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed. - - - - - - - Allows you to provide a SpEL expression to - generate the file name of - the local (transferred) file. The root - object of the SpEL - evaluation is the request Message, but the name of the original - remote file is also provided as the 'remoteFileName' variable. - For example, a valid expression would be: - "#remoteFileName.toUpperCase() + headers.foo". - Only used with 'get' and 'mget' commands. - - - - - - - Identifies directory path (e.g., - "/local/mytransfers") where file will be - transferred TO. - This attribute is mutually exclusive with 'local-directory-expression'. - - - - - - - Specifies SpEL expression to - generate the directory path where file will be - transferred TO, when using 'get' and 'mget' commands. - The root object of the SpEL evaluation is the request Message, - but the name of the source - remote directory is also provided as the 'remoteDirectory' variable. - For example, a valid expression might be: - "'/local/' + #remoteDirectory.toUpperCase() + headers.foo". - Only used with 'get' and 'mget' commands. - This attribute is mutually exclusive with 'local-directory'. - - - - - - - Tells this adapter if local directory must be - auto-created if it - doesn''t exist. Default is TRUE. - - - - - - - Specifies the order for invocation when this - endpoint is connected as a - subscriber to a channel. This is - particularly relevant when that channel - is using a "failover" - dispatching strategy, or when a failure in the - delivery to one - subscriber should signal that - the message should not be sent to - subscribers with a higher 'order' - attribute. It has no effect when - this - endpoint itself is a Polling Consumer for a channel with a - queue. - - - - - - - Specify whether this outbound gateway must return a non-null value. This value is - 'true' by default, and a ReplyRequiredException will be thrown when - the underlying service returns a null value. - - - - - - - - - - - - - - - - - - - - - - - - - Identifies channel attached to this adapter. - The channel to which messages will be sent - by this adapter. - - - - - - - Allows you to provide a file name pattern to - determine the file names - that need to be scanned. - This is based on - simple pattern matching (e.g., "*.txt, fo*.txt" - etc.) - - - - - - - Allows you to provide a Regular Expression to - determine the file names - that need to be scanned. - (e.g., - "f[o]+\.txt" etc.) - - - - - - - - - - - - Allows you to specify a reference to a - [org.springframework.integration.file.filters.FileListFilter] - bean. This filter is applied to files on the remote server and - only files that pass the filter are retrieved. - - - - - - - - - - - - - - - - - - - - Identifies the remote temporary directory path (e.g., "/remote/temp/mytransfers") - - - - - - - - - - - - - - - - - - - - - - - - Allows you to provide remote file/directory - separator character. DEFAULT: - '/' - - - - - - - Identifies the remote directory path (e.g., "/remote/mytransfers") - Mutually exclusive with 'remote-directory-expression'. - - - - - - - Specify a SpEL expression which - will be used to evaluate the directory - path to where the files will be transferred - (e.g., "headers.['remote_dir'] + '/myTransfers'" for outbound endpoints) - There is no root object (message) for inbound endpoints - (e.g., "@someBean.fetchDirectory"); - - - - - - - - - - - Extension used when downloading files. We change - it right after we know it's downloaded. - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/SftpFileAnnouncer.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/SftpFileAnnouncer.java deleted file mode 100644 index 3c32fd3d525..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/SftpFileAnnouncer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp; - -import java.io.File; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.stereotype.Component; - -/** - * @author Josh Long - * @author Gary Russell - */ -@Component("sftpAnnouncer") -public class SftpFileAnnouncer { - - private final Log logger = LogFactory.getLog(getClass()); - - @ServiceActivator - public void announceFile(File file) { - logger.info("New file from the remote host has arrived: " + file.getAbsolutePath()); - } - -} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/SftpTestSupport.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/SftpTestSupport.java deleted file mode 100644 index 4b407725b69..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/SftpTestSupport.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2016-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp; - -import java.io.File; -import java.util.Collections; - -import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory; -import org.apache.sshd.server.SshServer; -import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; -import org.apache.sshd.sftp.client.SftpClient; -import org.apache.sshd.sftp.server.SftpSubsystemFactory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; - -import org.springframework.integration.file.remote.RemoteFileTestSupport; -import org.springframework.integration.file.remote.session.CachingSessionFactory; -import org.springframework.integration.file.remote.session.SessionFactory; -import org.springframework.integration.sftp.server.ApacheMinaSftpEventListener; -import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; - -/** - * Provides an embedded SFTP Server for test cases. - * - * @author David Turanski - * @author Gary Russell - * @author Artem Bilan - * - * @since 4.3 - */ -public class SftpTestSupport extends RemoteFileTestSupport { - - private static SshServer server; - - private static final ApacheMinaSftpEventListener EVENT_LISTENER - = new ApacheMinaSftpEventListener(); - - @Override - public String getTargetLocalDirectoryName() { - return targetLocalDirectory.getAbsolutePath() + File.separator; - } - - @Override - public String prefix() { - return "sftp"; - } - - @BeforeAll - public static void createServer() throws Exception { - server = SshServer.setUpDefaultServer(); - server.setPasswordAuthenticator((username, password, session) -> true); - server.setPort(0); - server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath())); - SftpSubsystemFactory sftpFactory = new SftpSubsystemFactory(); - EVENT_LISTENER.setApplicationEventPublisher((ev) -> { - // no-op - }); - sftpFactory.addSftpEventListener(EVENT_LISTENER); - server.setSubsystemFactories(Collections.singletonList(sftpFactory)); - server.setFileSystemFactory(new VirtualFileSystemFactory(getRemoteTempFolder().toPath())); - server.start(); - port = server.getPort(); - } - - public static SessionFactory sessionFactory() { - DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(false); - factory.setHost("localhost"); - factory.setPort(port); - factory.setUser("foo"); - factory.setPassword("foo"); - factory.setAllowUnknownKeys(true); - CachingSessionFactory cachingSessionFactory = new CachingSessionFactory<>(factory); - cachingSessionFactory.setTestSession(true); - return cachingSessionFactory; - } - - public static ApacheMinaSftpEventListener eventListener() { - return EVENT_LISTENER; - } - - @AfterAll - public static void stopServer() throws Exception { - server.stop(); - File hostKey = new File("hostkey.ser"); - if (hostKey.exists()) { - hostKey.delete(); - } - } - -} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserCachingTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserCachingTests-context.xml deleted file mode 100644 index b7135618f4e..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserCachingTests-context.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserCachingTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserCachingTests.java deleted file mode 100644 index aaf81908809..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserCachingTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.file.remote.session.CachingSessionFactory; -import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Gunnar Hillert - * @author Gary Russell - */ -@SpringJUnitConfig -@DirtiesContext -public class InboundChannelAdapterParserCachingTests { - - @Autowired - private Object cachingAdapter; - - @Autowired - private Object nonCachingAdapter; - - @Test - public void cachingAdapter() { - Object sessionFactory = - TestUtils.getPropertyValue(cachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory"); - assertThat(sessionFactory.getClass()).isEqualTo(CachingSessionFactory.class); - } - - @Test - public void nonCachingAdapter() { - Object sessionFactory = - TestUtils.getPropertyValue(nonCachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory"); - assertThat(sessionFactory.getClass()).isEqualTo(DefaultSftpSessionFactory.class); - } - -} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context-fail-autocreate.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context-fail-autocreate.xml deleted file mode 100644 index 27e4014c4ce..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context-fail-autocreate.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml deleted file mode 100644 index 082dfb56996..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java deleted file mode 100644 index dea2aaad7be..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import java.io.File; -import java.util.Collection; -import java.util.Comparator; -import java.util.concurrent.PriorityBlockingQueue; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.Lifecycle; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.expression.Expression; -import org.springframework.integration.endpoint.SourcePollingChannelAdapter; -import org.springframework.integration.file.filters.FileListFilter; -import org.springframework.integration.metadata.MetadataStore; -import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer; -import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.PollableChannel; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author Gunnar Hillert - * @author Artem Bilan - */ -public class InboundChannelAdapterParserTests { - - @BeforeEach - public void prepare() { - new File("foo").delete(); - } - - @Test - public void testAutoStartup() { - ConfigurableApplicationContext context = - new ClassPathXmlApplicationContext("SftpInboundAutostartup-context.xml", this.getClass()); - - SourcePollingChannelAdapter adapter = context.getBean("sftpAutoStartup", SourcePollingChannelAdapter.class); - assertThat(adapter.isRunning()).isFalse(); - context.close(); - } - - @Test - public void testWithLocalFiles() { - ConfigurableApplicationContext context = - new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass()); - assertThat(new File("src/main/resources").exists()).isTrue(); - - Object adapter = context.getBean("sftpAdapterAutoCreate"); - assertThat(adapter instanceof SourcePollingChannelAdapter).isTrue(); - SftpInboundFileSynchronizingMessageSource source = - (SftpInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(adapter, "source"); - assertThat(source).isNotNull(); - - PriorityBlockingQueue blockingQueue = - TestUtils.getPropertyValue(adapter, "source.fileSource.toBeReceived", PriorityBlockingQueue.class); - Comparator comparator = blockingQueue.comparator(); - - assertThat(comparator).isNotNull(); - SftpInboundFileSynchronizer synchronizer = - TestUtils.getPropertyValue(source, "synchronizer", SftpInboundFileSynchronizer.class); - assertThat(TestUtils.getPropertyValue(synchronizer, "remoteDirectoryExpression", Expression.class) - .getExpressionString()).isEqualTo("'/foo'"); - assertThat(TestUtils.getPropertyValue(synchronizer, "localFilenameGeneratorExpression")).isNotNull(); - assertThat(TestUtils.getPropertyValue(synchronizer, "preserveTimestamp", Boolean.class)).isTrue(); - String remoteFileSeparator = (String) TestUtils.getPropertyValue(synchronizer, "remoteFileSeparator"); - assertThat(TestUtils.getPropertyValue(synchronizer, "temporaryFileSuffix", String.class)).isEqualTo(".bar"); - assertThat(TestUtils.getPropertyValue(synchronizer, "remoteFileMetadataStore")) - .isSameAs(context.getBean(MetadataStore.class)); - assertThat(TestUtils.getPropertyValue(synchronizer, "metadataStorePrefix", String.class)) - .isEqualTo("testPrefix"); - assertThat(remoteFileSeparator).isNotNull(); - assertThat(remoteFileSeparator).isEqualTo("."); - PollableChannel requestChannel = context.getBean("requestChannel", PollableChannel.class); - ((Lifecycle) adapter).start(); - assertThat(requestChannel.receive(10000)).isNotNull(); - FileListFilter acceptAllFilter = context.getBean("acceptAllFilter", FileListFilter.class); - @SuppressWarnings("unchecked") - Collection> filters = - TestUtils.getPropertyValue(source, "fileSource.scanner.filter.fileFilters", Collection.class); - assertThat(filters).contains(acceptAllFilter); - assertThat(source.getMaxFetchSize()).isEqualTo(42); - context.close(); - } - - @Test - public void testAutoChannel() { - ConfigurableApplicationContext context = - new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass()); - // Auto-created channel - MessageChannel autoChannel = context.getBean("autoChannel", MessageChannel.class); - SourcePollingChannelAdapter autoChannelAdapter = context.getBean("autoChannel.adapter", - SourcePollingChannelAdapter.class); - assertThat(TestUtils - .getPropertyValue(autoChannelAdapter, "source.synchronizer.remoteDirectoryExpression", - Expression.class) - .getExpressionString()).isEqualTo("/foo"); - assertThat(TestUtils.getPropertyValue(autoChannelAdapter, "outputChannel")).isSameAs(autoChannel); - assertThat(TestUtils.getPropertyValue(autoChannelAdapter, "source.maxFetchSize")).isEqualTo(Integer.MIN_VALUE); - context.close(); - } - - @Test - public void testLocalDirAutoCreated() { - assertThat(new File("foo").exists()).isFalse(); - ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( - "InboundChannelAdapterParserTests-context.xml", getClass()); - assertThat(new File("foo").exists()).isTrue(); - context.close(); - } - - @Test - public void testLocalDirAutoCreateFailed() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext( - "InboundChannelAdapterParserTests-context-fail-autocreate.xml", - getClass())); - } - - @AfterEach - public void cleanUp() { - new File("foo").delete(); - } - -} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserCachingTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserCachingTests-context.xml deleted file mode 100644 index 61221bed2ba..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserCachingTests-context.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserCachingTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserCachingTests.java deleted file mode 100644 index 207eab9a723..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserCachingTests.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.file.remote.session.CachingSessionFactory; -import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Fisher - * @author Gunnar Hillert - * @author Gary Russell - */ -@SpringJUnitConfig -@DirtiesContext -public class OutboundChannelAdapterParserCachingTests { - - @Autowired private Object cachingAdapter; - - @Autowired private Object nonCachingAdapter; - - @Test - public void cachingAdapter() { - Object sessionFactory = TestUtils.getPropertyValue(cachingAdapter, "handler.remoteFileTemplate.sessionFactory"); - assertThat(sessionFactory.getClass()).isEqualTo(CachingSessionFactory.class); - } - - @Test - public void nonCachingAdapter() { - Object sessionFactory = TestUtils.getPropertyValue(nonCachingAdapter, "handler.remoteFileTemplate.sessionFactory"); - assertThat(sessionFactory.getClass()).isEqualTo(DefaultSftpSessionFactory.class); - } - -} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail.xml deleted file mode 100644 index d301c6d5e9d..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context-fail.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml deleted file mode 100644 index 6da6762fbca..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java deleted file mode 100644 index b585b7fb75d..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import java.nio.charset.StandardCharsets; -import java.util.Iterator; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.integration.channel.PublishSubscribeChannel; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.file.FileNameGenerator; -import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; -import org.springframework.integration.file.remote.session.CachingSessionFactory; -import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; -import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Oleg Zhurakousky - * @author Gary Russell - * @author David Turanski - * @author Gunnar Hillert - * @author Artem Bilan - */ -@SpringJUnitConfig -@DirtiesContext -public class OutboundChannelAdapterParserTests { - - private static volatile int adviceCalled; - - @Autowired - ApplicationContext context; - - @Test - public void testOutboundChannelAdapterWithId() { - EventDrivenConsumer consumer = this.context.getBean("sftpOutboundAdapter", EventDrivenConsumer.class); - PublishSubscribeChannel channel = this.context.getBean("inputChannel", PublishSubscribeChannel.class); - assertThat(TestUtils.getPropertyValue(consumer, "inputChannel")).isEqualTo(channel); - assertThat(consumer.getComponentName()).isEqualTo("sftpOutboundAdapter"); - FileTransferringMessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", - FileTransferringMessageHandler.class); - String remoteFileSeparator = - TestUtils.getPropertyValue(handler, "remoteFileTemplate.remoteFileSeparator", String.class); - assertThat(remoteFileSeparator).isNotNull(); - assertThat(remoteFileSeparator).isEqualTo("."); - assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryFileSuffix", String.class)) - .isEqualTo(".bar"); - Expression remoteDirectoryExpression = - TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor.expression", - Expression.class); - assertThat(remoteDirectoryExpression) - .isNotNull() - .isInstanceOf(LiteralExpression.class); - assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor")) - .isNotNull(); - assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator")) - .isEqualTo(this.context.getBean("fileNameGenerator")); - assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8); - CachingSessionFactory sessionFactory = - TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory", CachingSessionFactory.class); - DefaultSftpSessionFactory clientFactory = - TestUtils.getPropertyValue(sessionFactory, "sessionFactory", DefaultSftpSessionFactory.class); - assertThat(TestUtils.getPropertyValue(clientFactory, "host")).isEqualTo("localhost"); - assertThat(TestUtils.getPropertyValue(clientFactory, "port")).isEqualTo(2222); - assertThat(TestUtils.getPropertyValue(handler, "order")).isEqualTo(23); - //verify subscription order - @SuppressWarnings("unchecked") - Set handlers = (Set) TestUtils - .getPropertyValue( - TestUtils.getPropertyValue(channel, "dispatcher"), - "handlers"); - Iterator iterator = handlers.iterator(); - assertThat(iterator.next()) - .isSameAs(TestUtils.getPropertyValue( - this.context.getBean("sftpOutboundAdapterWithExpression"), "handler")); - assertThat(iterator.next()).isSameAs(handler); - assertThat(TestUtils.getPropertyValue(handler, "chmod")).isEqualTo(384); - } - - @Test - public void testOutboundChannelAdapterWithWithRemoteDirectoryAndFileExpression() { - EventDrivenConsumer consumer = - this.context.getBean("sftpOutboundAdapterWithExpression", EventDrivenConsumer.class); - assertThat(TestUtils.getPropertyValue(consumer, "inputChannel")) - .isEqualTo(this.context.getBean("inputChannel")); - assertThat(consumer.getComponentName()).isEqualTo("sftpOutboundAdapterWithExpression"); - FileTransferringMessageHandler handler = TestUtils - .getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class); - SpelExpression remoteDirectoryExpression = - TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor.expression", - SpelExpression.class); - assertThat(remoteDirectoryExpression).isNotNull(); - assertThat(remoteDirectoryExpression.getExpressionString()).isEqualTo("'foo' + '/' + 'bar'"); - FileNameGenerator generator = - TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator", FileNameGenerator.class); - Expression fileNameGeneratorExpression = TestUtils.getPropertyValue(generator, "expression", Expression.class); - assertThat(fileNameGeneratorExpression).isNotNull(); - assertThat(fileNameGeneratorExpression.getExpressionString()).isEqualTo("payload.getName() + '-foo'"); - assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8); - assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor")) - .isNull(); - } - - @Test - public void testOutboundChannelAdapterWithNoTemporaryFileName() { - Object consumer = this.context.getBean("sftpOutboundAdapterWithNoTemporaryFileName"); - FileTransferringMessageHandler handler = TestUtils - .getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class); - assertThat((Boolean) TestUtils.getPropertyValue(handler, "remoteFileTemplate.useTemporaryFileName")).isFalse(); - } - - @Test - public void advised() { - Object consumer = this.context.getBean("advised"); - MessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageHandler.class); - handler.handleMessage(new GenericMessage<>("foo")); - assertThat(adviceCalled).isEqualTo(1); - } - - @Test - public void testFailWithRemoteDirAndExpression() { - assertThatExceptionOfType(BeanDefinitionStoreException.class) - .isThrownBy(() -> - new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", - getClass())) - .withMessageContaining("Only one of 'remote-directory'"); - } - - public static class FooAdvice extends AbstractRequestHandlerAdvice { - - @Override - protected Object doInvoke(ExecutionCallback callback, Object target, Message message) { - adviceCalled++; - return null; - } - - } - -} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundAutostartup-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundAutostartup-context.xml deleted file mode 100644 index a0f06d92df3..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundAutostartup-context.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml deleted file mode 100644 index 622055aa60f..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java deleted file mode 100644 index 1a46896ef09..00000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2002-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.integration.sftp.config; - -import java.lang.reflect.Method; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.expression.Expression; -import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.file.FileNameGenerator; -import org.springframework.integration.file.filters.RegexPatternFileListFilter; -import org.springframework.integration.file.filters.SimplePatternFileListFilter; -import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Command; -import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Option; -import org.springframework.integration.file.remote.session.CachingSessionFactory; -import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; -import org.springframework.integration.sftp.gateway.SftpOutboundGateway; -import org.springframework.integration.test.util.TestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author Gunnar Hillert - * @author Artem Bilan - * - * @since 2.1 - * - */ -@SpringJUnitConfig -@DirtiesContext -public class SftpOutboundGatewayParserTests { - - @Autowired - AbstractEndpoint gateway1; - - @Autowired - AbstractEndpoint gateway2; - - @Autowired - AbstractEndpoint gateway3; - - @Autowired - AbstractEndpoint gateway4; - - @Autowired - AbstractEndpoint advised; - - @Autowired - AbstractEndpoint noExpressionLS; - - @Autowired - AbstractEndpoint noExpressionPUT; - - @Autowired - AbstractEndpoint noExpressionGET; - - @Autowired - FileNameGenerator generator; - - private static volatile int adviceCalled; - - @Test - public void testGateway1() { - SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1, - "handler", SftpOutboundGateway.class); - assertThat(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator")).isEqualTo("X"); - assertThat(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "outputChannel")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue")) - .isEqualTo("local-test-dir"); - assertThat((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory")).isFalse(); - assertThat(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class)).isTrue(); - assertThat(TestUtils.getPropertyValue(gateway, "filter")).isNotNull(); - assertThat(TestUtils.getPropertyValue(gateway, "command")).isEqualTo(Command.LS); - @SuppressWarnings("unchecked") - Set